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. waiting queue
        1. Definition
        2. Initialize waiting queue
        3. Wait Queue Entry API
        4. Sleep wait API
        5. Wake-up API
    4. Delay 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 whether 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. I/O model
    1. I/O operation process
    2. Classification of I/O models
      1. Synchronous blocking I/O
      2. Synchronous non-blocking I/O
      3. I/O multiplexing
      4. Signal-driven I/O
      5. Asynchronous I/O
    3. Wait queues implement 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. Step
        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: Device Driver Positioning
    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 API functions provided by the driver
  7. Optimize driver stability and efficiency
    1. Detect ioctl commands
    2. Check whether the transfer 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

Words 17.4k
Views
Visitors

Timeline

Timeline

2025-11-13

init

This article introduces key kernel mechanisms in Linux advanced character device driver development, including the principle and application of the container_of macro, the creation, initialization, addition, deletion, and traversal operations of the kernel's circular doubly linked list data structure, as well as the implementation of kernel sleep mechanisms and wait queues, providing a foundation for blocking I/O and resource management in character device drivers.

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 Topics
6. Interrupts
7. Platform Bus
8. Device Tree
9. Device Model
10. Hotplug
11. pinctrl Subsystem
12. GPIO subsystem
13. Input subsystem
14. 1-Wire
15. I2C
16. SPI
17. UART
18. PWM
19. RTC
20. Watchdog
21. CAN
22. Network devices
23. ADC
24. IIO
25. USB
26. LCD

Kernel Tools and Helper Functions

container_of macro

1234567
// 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))); })
  • ptrA pointer to a member within a structure.

  • typeThe target structure type.

  • memberThe name of the member in the structure.

  • BUILD_BUG_ON_MSG(...)Compile-time type checking ensures that ptr matches the type of the structure member, avoiding type errors.

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

  • offsetof(type, member)A standard macro that calculates the byte offset of a member within a structure.

container_ofThrough the member pointer__mptrsubtract the offset of that member in the structureoffsetof(type, member), thus obtaining the starting address of the structure.

linked list

A driver manages multiple devices. Suppose there are 5 devices; the driver may need to track each device, which requires a linked list.

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

123
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 embedstruct list_headfield.

Example

Let’s create a car linked list:

12345
struct car {	int door_number;	char *color;	char *model;};

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

123456
struct car {	int door_number;	char *color;	char *model; 	struct list_head list; /*Kernel list structure */}

Createstruct list_headvariable, which always points to the head of the linked list (the first element). This instance of list_head is not associated with any car, but is a special instance:

1
static LIST_HEAD(carlist);

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

1234567891011121314
#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 fill each field */[...]list_add(&redcar->list, &carlist) ;list_add(&bluecar->list, &carlist) ;

Creating and initializing a linked list

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

  1. Dynamic method

The dynamic method isstruct list_headconsisting of, usingINIT_LIST_HEADMacro initialization:

12
struct list_head mylist;INIT_LIST_HEAD(&mylist);

INIT_LIST_HEAD is defined as follows

123456789101112
/** * 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

1234
#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) in 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 list_head field embedded in it. Taking a car as an example, the code is as follows:

1234
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:

12345678910111213141516171819202122232425262728293031
/* * 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:

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

The function to add a node to the tail of the linked list, the code is as follows:

123456789101112
/** * 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:

12
list_add_tail(&redcar->list, &carlist);list_add_tail(&bluecar->list, &carlist);

This pattern can be used to implement a queue (FIFO)

Deleting a linked list node

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

123456789101112
/** * 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 the node. The memory allocated to the node needs to be manually freed using kfree.

Linked list traversal

Using the macrolist_for_each_entry(pos, head, member)to traverse the linked list.

  • pos: Used for iteration.

  • head: The head node of the linked list.

  • member: data structure (in our case, it isstruct carthe linked list in the list defined instruct list_headthe name of.

123456789
struct car *acar; /* Loop counter*/int blue_car_num = 0;/* list is the name of the list_head structure in data structures. */list_for_each_entry(acar, carlist, list){	if(acar->color == "blue") 		blue_car_num++;}

list_for_each_entrydefined as follows:

1234567891011
/** * 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

Processes release the processor through a sleep mechanism, allowing it to handle other processes. The reason the processor sleeps may be due to perceived data availability or waiting for resource release.

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

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

waiting queue

The wait queue is actually used to handle blocked I/O, to wait for a specific condition to be met, and to sense the availability of data or resources.

Wait queues are a kernel mechanism for implementing blocking and wakeup,The waiting queue is based on a doubly circular linked list., where the two parts, the linked list head and the linked list entry, respectively represent the wait queue head and the wait queue element.

waiting queue
waiting queue

Definition

include/linux/wait.h

12345678910111213141516
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 waiting queue

APIPurposeDescription / ParametersNotes.
DECLARE_WAIT_QUEUE_HEAD(name)Statically define wait queue headGenerate one namednameThe global/static wait queue headMost common way
init_waitqueue_head(wq)Dynamically initialize the waiting queue headApplicable to wait queue heads in dynamically allocated structures.Memory must be allocated first.

Wait Queue Entry API

In most cases, these APIs are 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 a queue entrytask is usuallycurrentDynamically create an entry
add_wait_queue(head, entry)Add a wait entry to the wait queueNon-exclusive waitMostly used for low-level calls (not recommended for direct use)
add_wait_queue_exclusive(head, entry)Exclusive waitWhen waking up, only wakes 1 exclusive processUsed for blocking queues such as write operations
remove_wait_queue(head, entry)Remove an entry from the wait queueand add_wait_queue are paired

Sleep wait API

APIPurposeDescriptionReturn value / Notes
wait_event(wq, condition)If condition is not met → sleep (non-interruptible)Returns when condition is trueCannot be interrupted by signals
wait_event_timeout(wq, condition, timeout)Non-interruptible sleep + timeouttimeout is in jiffiesReturn remaining jiffies or 0 on timeout
wait_event_interruptible(wq, condition)Interruptible sleepInterruptible by signalsReturn when interrupted-ERESTARTSYS
wait_event_interruptible_timeout(wq, condition, timeout)Interruptible + timeoutReturn: >0 remaining time, 0 timeout, <0 interrupted by signal
wait_event_killable(wq, condition)Interrupted only by fatal signalsSafer than interruptibleInterrupted by kill signal
wait_event_killable_timeout(wq, condition, timeout)killable + timeoutsame as above

wait_event_interruptibleIt does not continuously poll, but only evaluates the condition when called. If the condition is false, the process will enterTASK_INTERRUPTIBLEstate and be removed from the run queue.

Afterwards, each time it is called in the wait queuewake_up_interruptible, the condition is rechecked. Ifwake_up_interruptibleWhen it runs and finds the condition true, the processes in the wait queue will be woken up and their state set toTASK_RUNNING. Processes are woken in the order they went to sleep.

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

If it is calledwake_uporwake_up_interruptible, and the condition is still FALSE, nothing will happen. If it is not calledwake_up(orwake_up_interruptible), the process will never be woken up.

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

APIPurposeDescription / ParametersWake-up rules
wake_up(&wq)Wake up all non-exclusive waiters in the queueDo not modify task stateSuitable for many readers, wake-up does not need to be restricted
wake_up_all(&wq)Wake up all waitersIncluding exclusiveForce wake up all tasks
wake_up_interruptible(&wq)WakeupTASK_INTERRUPTIBLETask stateAdapt to 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 wakes up only one

There are two types of waiters in the wait queue:

TypeHow did it come about?Typical scenarios
Non-exclusiveadd_wait_queue()Multiple readers (read)
exclusiveadd_wait_queue_exclusive()Writers, resource contention

The design goal of exclusive is:

Avoid the ‘thundering herd’

Delay 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 exact time, i.e., the date and time of 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; from the kernel’s perspective, it is called a kernel timer.

Kernel timers are divided into two different parts.

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

Linux kernel standard timers

Standard timers are kernel timers that run at jiffy granularity.

The hardware provides the kernel with a system timer to calculate elapsed time (i.e., a timing method based on a future time point, with the current moment as the starting point and a future moment as the ending point),The kernel can calculate and manage time only 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 stop after reaching the timing endpoint. To implement periodic timing, the timer must be restarted in the timer handler function.

Definition

The Linux kernel uses the timer_list structure to represent kernel timers.

include/linux/timer.h

1234567891011121314
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

123456789101112
// 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)

You can use the following code to define the timer and the 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>
timer parameterto be initializedstruct timer_listTimer object
callback parameterThe callback function executed after the timer expires, of typevoid (*)(struct timer_list *)
parameter flagsTimer flag bits (such asTIMER_DEFERRABLETIMER_PINNEDetc.)
FunctionInitialize a timer object to prepare it for first use, and set the callback function and flags.
Return valueNo return value (void)
DescriptionIt only completes initialization and does not start the timer; it must be used withadd_timer()ormod_timer()before it will 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>
timer parameteron the stackstruct timer_listTimer object
callback parameterCallback function executed when the timer expires.
parameter flagsTimer flag
FunctionInitialize a stack-allocated timer object
Return valueNo return value (void)
Notes.must be withdestroy_timer_on_stack()Use in pairs, otherwise it may cause kernel debugging 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>
timer parameteron the stackstruct timer_listTimer object
FunctionDestroy a passtimer_setup_on_stack()Initialized on-stack timer
Return valueNo return value (void)
Use caseOnly for timers allocated on the stack (not heap/global variables)
Notes.must be withtimer_setup_on_stack()Use in pairs

add_timer()

ItemDescription
Function definitionvoid add_timer(struct timer_list *timer);
Header file#include <linux/timer.h>
timer parameterAn initialized timer object must be set.expires(Expiration Time) andfunction(callback function)
FunctionRegister the timer with the kernel to start it. WhenexpiresCallback function triggered when the timer expires
Return valueNo return value (void)

del_timer()

ItemDescription
Function definitionint del_timer(struct timer_list *timer);
Header file#include <linux/timer.h>
timer parameterThe timer object to be deleted
FunctionDelete the timer from the kernel so that it no longer triggers. It does not wait for the callback to finish executing (if you need to wait, usedel_timer_sync()
Return valueSuccessfully deleted:1(timer is active) no longer in the active queue:0

Note: Delete the timer from the kernel so that it no longer triggers. If you want to wait for the callback to finish executing, usedel_timer_sync(), wait for the handler (even if executed on another CPU) to complete. You should not hold a lock that prevents the handler from completing, as this will cause a deadlock.

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

timer_pending()

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

ItemDescription
Function definitionstatic inline int timer_pending(const struct timer_list *timer);
Header file#include <linux/timer.h>
timer parameterwhose status is to be checkedstruct timer_listTimer object
FunctionDetermine whether the timer is currently in the “pending” (waiting to trigger) state
Return valueIf the timer has been added to the kernel timer queue (is timing), returns 1; otherwise returns 0
Notes.The caller must ensure that access to the timer is protected against concurrency (e.g., by locking or being in the same context)

mod_timer()

ItemDescription
Function definitionint mod_timer(struct timer_list *timer, unsigned long expires);
Header file#include <linux/timer.h>
timer parameterThe 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 activateIf activated, it willrestart the timer
Return valueBefore modification, the timer is activated:1
Before modification, the timer is not activated:0

When using add_Before the timer() function registers a timer with the Linux kernel, you also need to set the timer time, which is determined by the timer_determined by the expires parameter in the list structure, in units of ticks. The system tick frequency can be set through the menuconfig graphical interface during Linux compilation, and the specific path is as follows:

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

The system tick rates selectable in the current kernel code are 100Hz, 250Hz, 300Hz, and 1000Hz; by default, 300Hz is selected.

Global variable jiffies

The global variable jiffies is used to record the total number of ticks generated since system startup. jiffies_64 is used for 64-bit systems, while jiffies is used for 32-bit systems. At startup, the kernel initializes this variable to 0. After that, each clock interrupt handler increments the value of this variable. The value by which jiffies increases in one second is the configured number of system ticks. This variable is defined ininclude/linux/jiffies.hin the file (already included in timer.h, no need to include it again), the specific definition is as follows:

12
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 definitionFunction
int jiffies_to_msecs(const unsigned long j)willjiffiesparameter of typejconvert to the corresponding milliseconds
int jiffies_to_usecs(const unsigned long j)willjiffiesparameter of typejconvert to the corresponding microseconds
u64 jiffies_to_nsecs(const unsigned long j)willjiffiesparameter of typejconvert to 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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
#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", (unsigned long long)jiffies_to_msecs(get_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;        }        // After setting timer_test to 5s        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 make a timer, as shown in the following example:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
#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;        // Initialized 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 Timer (high-precision timer)**Enabled by the kernel configurationCONFIG_HIGH_RES_TIMERSoption, with the following features:

  • Nanosecond-level precision (based on ktime)
  • Suitable for high-precision periodic tasks
  • Runs in softirq context
  • Supports periodic or one-shot triggering

Compared with traditionaltimer_listthe difference:

Itemtimer_listhrtimer
Precisionjiffies levelNanosecond-level
Precision depends onHZhardware high-precision clock
Suitable forordinary delayshigh-precision periodic tasks
UnitSymbolEquivalent in 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 dynamics
Attosecondas10⁻¹⁸ sElectron motion inside atoms (related to the 2023 Nobel Prize in Physics)

Header file:

1
#include <linux/hrtimer.h>

Check whether HRT is available on the system.

  1. View the kernel configuration file, which should contain something like this:CONFIG_HIGH_RES_TIMERS=y
  2. viewcat /proc/timer_list | grep resolutionthe result of..resolutionThe item must display1 nsecs, the event handler must displayhrtimer_interrupts
  3. useclock_getressystem calls.
  4. In the kernel code, use#ifdef CONFIG_HIGH_RES_TIMERS

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

struct hrtimer

12345678910111213141516171819202122232425262728293031
/** * 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 hrtimer initialization, 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 the timer
Parameterstimer: timer
tim: time
mode: mode
Return value0 or 1

hrtimer_cancel()

ItemDescription
Prototypeint hrtimer_cancel(struct hrtimer *timer);
FunctionCancel the 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>
timer parameterto be canceledstruct hrtimerHigh-resolution timer object
FunctionAttempt to cancel a high-resolution timer (non-blocking)
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
Whether it blocks❌ Does not wait for callback execution to complete (non-blocking), can be called in interrupt context

hrtimer_try_to_cancel internally calls hrtimer_callback_running

hrtimer_callback_running()

Independently check whether the hrtimer callback function is still running:

ItemDescription
Function definitionstatic inline int hrtimer_callback_running(struct hrtimer *timer);
Header file#include <linux/hrtimer.h>
timer parameterwhose status is to be checkedstruct hrtimerHigh-resolution timer object
FunctionDetermine whether the hrtimer’s callback function is currently executing
Return valueIf the callback is executing, returns non-zero (true); otherwise returns 0 (false)
Whether it blocks❌ Does not block, only performs a status check
Use caseUsed to detect callback execution status, often used in conjunction withhrtimer_try_to_cancel()use
12345678
/* * 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()

ItemDescription
Prototypeu64 hrtimer_forward_now(struct hrtimer *timer, ktime_t interval);
FunctionUsed for periodic timing
Return valueNumber of forwards

Callback return value

1234
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

12345678910111213141516171819202122232425262728293031323334353637383940
#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 interrupts HZ times per second to schedule tasks again. If HZ is set to 1000, there will be 1000 kernel interrupts per second, preventing the CPU from staying idle for long periods, thus affecting CPU power consumption.

Now let’s take a lookKernels without a fixed or predefined tick, in these kernels,the tick is disabled until some task needs to be executed. Such kernels are called Tickless (no-tick) kernels.

In fact, tick activation is scheduled based on the next operation, so the 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, and it enables dynamic tick by,disabling the periodic tick until the next timer expires (when a new task is queued for processing)

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

  • In the idle state, if the next tick is farther away 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 and saves 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 a sleeping state, and cannot be scheduled. This is the reasonwhy delays in atomic context must use busy-wait loops. The kernel provides the Xdelay series of functions, which consume enough time in a busy loop (based on jiffies) to obtain the required delay.

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

For short delays in the microsecond range, it is recommended to useudelay(), becausendelay()The precision depends on the precision of the hardware timer (not necessarily guaranteed on embedded SoCs). For millisecond-level delays, usemsleep()series functions rather thanmdelay(), becausemdelay()It is busy waiting, which wastes CPU resources.

The timer handler (callback) executes in an atomic context, which means that sleeping is not allowed at all. This refers to all functions that may cause the calling program to sleep, such as allocating memory, locking mutexes, explicitly callingsleep()functions, etc.

Non-atomic context

In a non-atomic context, the kernel providessleep[_range]a series of functions; which one to use depends on how long the delay needs to be.

  • udelay(unsigned long usecs): Based on a busy-wait loop. If you need to sleep for a few microseconds (about 10 us or less), you should use this function.
  • usleep_range(unsigned long min, unsigned long max): It relies on hrtimer; for sleeping from a few microseconds to a few milliseconds (10 us to 20
    ms), it is recommended to use it, avoiding the use ofudelay()the busy-wait loop.
  • msleep(unsigned long msecs): Supported by jiffies/traditional timers. For long sleeps of more than a few milliseconds (10 ms+), use this function.

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

Calling user-space programs from the kernel

Consider the following example:

1234567891011121314151617181920212223242526272829303132333435363738394041424344
#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(struct work_struct *work){	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 ){	INIT_DELAYED_WORK(&delayed_shutdown, delayed_shutdown);	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.cinside. Its usage is very simple. It is used by the kernel, for example, for module loading/unloading and Cgroup management.

I/O model

I/O operation process

A complete I/O process typically includes the following key steps:

  • I/O call: The application initiates an I/O request to the kernel through the system call interface (e.g., reading a file).
  • I/O execution: After receiving the request, the kernel operates the hardware through drivers to complete the specific I/O (e.g., reading data from disk), and returns the final result to user space.

A complete I/O process needs to include the following three steps:

  1. A user-space application initiates an I/O call request (system call) to the kernel.
  2. The OS kernel prepares the data, loading the I/O device data into the kernel buffer.
  3. The operating system copies data, copying the data from the kernel buffer to the user process buffer.

Classification of I/O models

In actual development, I/O operations often become a key factor affecting program performance. Suppose there is a scenario: read 100MB of data from disk and process it. Reading the data takes 20 seconds, and processing the data also takes 20 seconds. If the most traditional sequential flow is adopted—read first, then process—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 I/O programming model comes in.

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

  • Synchronous I/O
    • Blocking I/O
    • Non-blocking I/O
    • I/O multiplexing
    • Signal-driven I/O
  • Asynchronous I/O

The difference between synchronous and asynchronous lies inwhether to wait for the execution result of I/O, or rather, who completes the copying of data to user space.?。

Synchronous I/O:The final action of copying data from the kernel to user space is completed by the user thread whenread()the call is made

Asynchronous I/O:

  • User mode initiates an I/O request → returns immediately
  • The kernel completes the I/O in the background
  • Data has been copied to user space
  • The kernel notifies the process: I/O complete

Synchronous blocking I/O

When a process performs an I/O operation (such as a read operation), it first issues a system call, thereby switching to kernel space for processing,When the data in kernel space is not ready, the process will be blocked and will not continue executing downward, 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, where user space processes the data.

Blocking I/O model
Blocking I/O model

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

Synchronous non-blocking I/O

Unlike the blocking I/O model, when non-blocking I/O performs an I/O operation,if the kernel data is not ready, the kernel will immediately return an err to the processand will not block; if the kernel-space data is ready, the kernel will immediately return the data to the process in user space.

Synchronous non-blocking I/O
Synchronous non-blocking I/O

The advantage of non-blocking I/O is high efficiency; it can do other things in the same amount of time. However, its disadvantage is also obvious: when it needs to frequently check the data readiness status, it may cause high CPU usage. To solve this problem, non-blocking I/O is usually used in combination with I/O multiplexing technology.

I/O multiplexing

The select(), poll(), and epoll() functions are mechanisms for implementing I/O multiplexing.

I/O multiplexing allowsa single process to monitor multiple descriptors. When a descriptor is found to be ready, it will notify the program to perform the corresponding read/write operation.

Take 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 the timeout period 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(such as readable or writable). If an event is detected, it returns immediately; ifno event is detected, the process will enter a blocking state and sleepuntil any descriptor becomes ready or a timeout occurs.

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

I/O multiplexing
I/O multiplexing

The advantage of I/O multiplexing is that one process/thread can simultaneously monitor and handle multiple I/O streams, greatly improving efficiency. However, I/O multiplexing is not a panacea,Although I/O multiplexing can monitor multiple I/O streams, the processing of results can only be done sequentially in practice, and is moresuitable for I/O-intensive scenarios where each I/O stream has a small amount of data and arrival times are scattered.(such as network chat).

In addition, the descriptors monitored by select have an upper limit (generally the maximum number of descriptors does not exceed 1024), andit is necessary to traverse to find out which IO produced the data. Therefore, when there are many IOs, the efficiency is not high (this problem is solved by epoll).

Signal-driven I/O

Signal-driven I/O means thatthe process tells the kernel in advance that when an event occurs on a descriptor, the kernel should send a SIGIO signal to the process to notify it, and the process can handle the event in 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 mode for the corresponding descriptor. When data is ready, the process receives a SIGIO signal and can call I/O operation functions in 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 uses itaio_read()to read data,if the data is not ready, it returns immediately without blocking

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

Asynchronous I/O
Asynchronous I/O

However, for Linux AIO, Linux AIO only supports direct I/O modestorage files (storage file), and is mainly used inthe database niche

Andio_uringsupports storage files and network files (network sockets), and also supports more asynchronous system calls (accept/openat/stat/...), rather than onlyread/writesystem calls.

Wait queues implement 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: Call where blocking is requiredwait_event(), causing the process to enter a sleep state.

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

Driver:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
#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 32struct 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 { // Open 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 waiting 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

1234567891011121314
$ 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 calledHello World$ echo "Hello Linux" > /dev/waitqueue_test0[   43.618554] waitqueue_test_open is called[   43.619516] waitqueue_test_release is calledHello Linux

Non-blocking access

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

12345
int fd;int data = 0;fd = open("/dev/xxx_dev", O_RDWR);/* Open in blocking mode */ret = read(fd, &data, sizeof(data));/* Read data */

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

1234
int fd;int data = 0;fd = open("/dev/xxx_dev", O_RDWR | O_NONBLOCK); /*Open in non-blocking mode */ret = read(fd, &data, sizeof(data)); /* Read data */

Use the open function to open “/dev/xxx_dev” device file, add the parameter “O__NONBLOCK”, indicating that the device is opened in non-blocking mode, so reading data from the device is non-blocking.

Driver

Through the f in the file structure_In flags, check whether the application passes O_Open in NONBLOCK mode

12345678
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 descriptorsOnce a file descriptor is ready, the application is notified to perform the corresponding read/write operation. When no file descriptor is ready, the application is blocked, thereby releasing 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 the file descriptors.
  • epoll changes active polling into passive notification; when an event occurs, it passively receives the notification.

Linux application-layer poll

Itemcontent
FunctionMonitor read/write events or exception events of multiple file descriptors
Prototypeint poll(struct pollfd *fds, nfds_t nfds, int timeout);
Parameters-fds: struct pollfd array, describing the monitored file descriptors and events
-nfds: number of monitored fds
-timeout: timeout (ms)
>0: wait for the specified time;
= 0: return immediately;
-1: block forever until an event occurs
Return value>0: returns revents ≠ 0 the number of fds
=0: timeout
-1: failure

struct pollfd

12345
struct pollfd {    int   fd;       // monitored file descriptors    short events;   // events to be monitored    short revents;  // events returned by the kernel};	

events and revents of pollfd

Event typeConstantAs the value of eventsAs the value of reventsDescription
Read eventPOLLINHas normal data to read
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 eventPOLLERRAn error occurred
Error eventPOLLHUPA hang occurred
Error eventPOLLNVALDescriptor is not an open file

Driver-level poll

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

If you need to implement passive waiting (without wasting CPU cycles when monitoring character devices), you must implementpoll()function, whenever a user-space program executes a system call on the file associated with the deviceselect()orpoll()it will be calledpoll()function.

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

Implementation in the driver

poll_wait()

Itemcontent
FunctionAdd the driver’s wait queue to the poll_table, used 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 the application layer)
Return valueNone
FeaturesWill not block! It only registers the wait queue.

poll_wait()According to the events registered in thestruct poll_tablestructure (passed as the third parameter), add the device associated with 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 sleeps).

The user process can runpoll()select()orepoll()The system call adds a set of files that need to be waited on to the wait queue, to learn whether any related device is 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, put the processes to sleep before these events occur, and register the driver as one that can wake up the processes.

Step

  1. For each event type (read, write, exception) that requires passive waiting, declare a wait queue; when no data is readable or the device is not writable, put the task into that queue:
12
static DECLARE_WAIT_QUEUE_HEAD(my_wq);static DECLARE_WAIT_QUEUE_HEAD(my_rq);
  1. Implement the poll function like this.
12345678910111213
#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 is writable, notify the wait queue:
12
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 the readable event:

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

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

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

example

app read.c

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
#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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
#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 32struct 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 waiting 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:

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556
$ 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 query the device status. Once the device is ready, it triggers the SIGIO signal, which in turn calls the registered handler function.

To implement signal-driven I/O, the application and the driver need to 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 a handler for the SIGIO signal.
  • Step 2: Set the process that can receive this signal (fcntl function)
  • Step 3: Enable signal-driven I/O. Usually use the F_SETFL command of the fcntl function to set the FASYNC flag.

User space can use either O_ASYNC or FASYNC

12345678910
// /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, the fasync function in the driver is triggered. So first implement the fasync function in the file_operations structure. The function prototype is as follows:

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

The fasync function in the driver calls fasync_helper function to operate on the fasync_struct structure; 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:

12345678
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_structIt is 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

What is stored in the driver is linked list head pointer

  • kill_fasync()Traverse 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 this time 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: the one to operate onfasync_struct
    • sig: the signal to send
    • band: set to POLLIN when readable, and set to POLLOUT when writable

example

app

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
#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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
#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 64struct 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 waiting 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:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566
$ 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 application-layer glibc and can be independent of the Linux kernel.

Reference:

Forio_uringYou can refer to this article on this site.

Linux kernel printing

dmesg

In the terminal, you can use the dmesg command to obtain kernel print information. The specific usage of this command is as follows:
dmesg command

  • Full English name: display message (display information)
  • Function: The kernel stores print information in a 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, --display timestamp

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

Combine 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 ring buffer ‘log_buf’. In order to conveniently read kernel print information in user space, the Linux kernel driver maps this ring buffer to the file node kmsg under the /proc directory.

When reading the Log Buffer via cat or other applications, you can continuously wait for new logs, sothe method of accessing /proc/kmsg is suitable for long-term log reading, and as soon as a new log appears, it can be printed out.

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

1
cat /proc/kmsg

Adjust kernel print level

The kernel’s 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 than the latter (lower value), the message will be printed to the console immediately.

You can check the log level parameters like this: You can control the output of printed logs 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’ correspond to console_loglevel、default_message_loglevel、minimum_console_loglevel、default_console_loglevel, the specific types are described as follows:

  1. console_loglevel
    • Description: Controls which levels of messages can be output to Terminal(console)。
    • Rule: Only when the message’s log priority is higher than console_loglevel will it be displayed on the terminal.
    • Example: “7” means messages of all levels (0~7) are allowed 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 printed message level is warning
  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 is emergency (emerg)
  4. default_console_loglevel
    • Description: The default value of console_loglevel when the kernel starts.
    • Example: “7” means by default, it allows all levels to be output to the terminal.

include/linux/printk.c

1234567
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

123456789101112131415161718192021222324252627282930313233343536373839
/* 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 is unusable
1KERN_ALERTAlert, action must be taken immediately
2KERN_CRITCritical
3KERN_ERRError
4KERN_WARNINGWarning
5KERN_NOTICENotice
6KERN_INFOInfo
7KERN_DEBUGDebug Messages

Modify kernel print level:

123
echo 0 4 1 7 > /proc/sys/kernel/printk# If you see 'permission denied', use the following command:echo "7 4 1 7" | sudo tee /proc/sys/kernel/printk

Before printing a message, 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 omittedprintk("This is anerror\n"), then the kernel will, according toCONFIG_DEFAULT_MESSAGE_LOGLEVELthe configuration option (this is the default
kernel log level) provide a debug level to the function.

In fact, you can use the following macros, whose names are more meaningful. They are wrappers for the previously defined content 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: Device Driver Positioning

Application layer lseek()

All open files have acurrent file offset, hereinafter referred to as cfo. cfo is usually anon-negative integer, used to indicate the number of bytes from the beginning of the file to the current position of the file. Read and write operations usually 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 lseek function can be used to change the file’s cfo.

ItemDescription
Function definitionoff_t lseek(int fd, off_t offset, int whence);
Header file#include <sys/types.h>
#include <unistd.h>
Parameters fdfile descriptor to be operated on
Parameters off_t offsetOffset,in bytesPositive and negative values indicate moving forward and backward, respectively
Parameters whencePosition base point, selectable SEEK_SET(beginning of file),SEEK_CUR(current pointer position),SEEK_END(end of file)
FunctionMove the file read/write pointer; get the file length; expand file space
Return valueSuccessReturns the new file offset, 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);
  • Function: File pointer offset operation (similar to lseek system call).
  • Parameters
    • file: File object.
    • offset: The 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
    • On error, returns a negative value (e.g.-EINVAL

Usage steps

  1. Use a switch statement to check each whence case. Since its values are limited, adjust newpos accordingly:
12345678910111213
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 whether newpos is valid:
12
if ( newpos < 0 )	return -EINVAL;
  1. Update f_pos using the new position.
1
filp->f_pos = newpos;
  1. Return the new file pointer position.
1
return newpos;

example

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
#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 32struct 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 a fixed buffer size, you can directly return what the kernel has already implementedfixed_size_llseek(file, offset, whence, KMEM_SIZE);

Test case:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
#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 64static 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:

12345678910111213141516171819202122232425262728
$ ./test_llseek.oopen /dev/llseek_test0[TEST] write "Hello llseek test!"write ret = 18[TEST] lseek SEEK_SET 0current offset = 0read (32 bytes): "Hello llseek test!\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"[TEST] lseek SEEK_CUR -6current offset = 26read (6 bytes): "\x00\x00\x00\x00\x00\x00"[TEST] lseek SEEK_END -5current offset = 27read (5 bytes): "\x00\x00\x00\x00\x00"[TEST] read until EOFseek to 0chunk (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 fdThe opened device file descriptor (e.g.,/dev/...
The op parameterI/O control command (usually constructed via_IO,_IOR,_IOW,_IOWRand other macros)
Parameter arg(optional)Data associated with the request command, which can beint*,void*,struct *etc.
FunctionExecute control commands on the device driver (non-data read/write type), used to configure hardware, obtain status, send control instructions, etc.
Return valueSuccess: usually 0(may also return other positive values, depending on request)
Failure: returns -1, and sets errno

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

12
| 31 30 | 29 ................ 16 | 15 ...... 8 | 7 .......... 0 || dir   |         size           |    type     |       nr       |
  • op[31:30]dirData (args) transfer direction (read/write)
  • op[29:16]sizeData (args) size
  • op[15:8]typeThe command 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 number segment of each character may be partially occupied).
  • op[7:0]nrThe sequence number of the command, which is an 8-bit number (sequence number, between 0-255)

The op parameter is ioctl composition macrosdefined, and the four composition macros are defined as follows:

  1. Define a command, but no parameter is needed:
12
#define _IO(type,nr)   _IOC(_IOC_NONE,(type),(nr),0)
  1. Define a command, the application reads parameters from the driver:
12
#define _IOR(type,nr,size)   _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
  1. Define a command, the application writes parameters to the driver:
12
#define _IOW(type,nr,size)   _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
  1. Define a command whose 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 parameters are described as follows:

  • typeThe command type, generally an ASCII code value, called the magic number. A driver generally uses one type.
  • nr: The sequence number under this command. A driver has multiple commands, generally their type and sequence number are different.
  • size: The type of args

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

123
#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 that only one integer can be passed; you can pass a structure pointer to implement “multiple parameters”.

SIZE = the size of the data structure of the arg parameter (in bytes).It is automatically encoded when the driver creates the ioctl command code.

Example:

1
_IOR('M', 1, int)

will automatically

1
sizeof(int) = 4

encode into op. If it is:

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

then:

1
sizeof(struct task_info)

The user calls

12
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));

of the kernel source code Documentation/driver-api/ioctl.rstWill be explained in detail in the following.

exampleeep_ioctl.h:

123456789101112131415161718192021222324252627
#ifndef PACKT_IOCTL_H#define PACKT_IOCTL_H/** A number needs to be selected 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 maximum32bytes*/#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()

12
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 a 64-bit kernel’

Calling an undefinedioctlcommand returns-ENOTTYerror

The kernel will use ioctl decomposition macro Parse cmd:

  • _IOC_DIR(cmd)— Data direction
  • _IOC_TYPE(cmd)— Device magic
  • _IOC_NR(cmd)— Command number
  • _IOC_SIZE(cmd)— Transfer data size

example

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
#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 ID    int major ;  //major device number    int minor ;  //secondary 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 functionDEFINE_TIMER(timer_test,fnction_test);//Define a timervoid 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 the timer time            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 the module from being unloaded 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 the 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 the registration of the character device to 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 classerr_class_create:       cdev_del(&dev1.cdev_test);                 //Delete cdeverr_chr_add:        unregister_chrdev_region(dev1.dev_num, 1); //Unregister device numbererr_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 API functions provided by the driver

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

Compile into library files,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 by composing macros, and there are corresponding decomposition macros to obtain each parameter. The four decomposition macros are as follows:

  • Parse the cmd command to obtain the command type:
1
_IOC_TYPE(cmd)
  • Parse the cmd command to determine the transmission direction of the data (args):
1
_IOC_DIR(cmd)
  • Parse the cmd command to get the command’s sequence number:
1
_IOC_NR(cmd)
  • Parse the cmd command to get the size of the data (args):
1
_IOC_SIZE(cmd)

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

Check whether the transfer address is reasonable.

access_ok()

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

Example:

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

Branch prediction optimization

Modern CPUs all have ICache and pipeline mechanisms. That is, when executing the current instruction, the ICache prefetches subsequent instructions to improve efficiency. However, if the result of a conditional branch is a jump to another instruction, then prefetching the next instruction is a waste of time.

likely and unlikely macros

And what we are going to use likely and unlikely macros, which will make the compiler always 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, and the specific definitions are as follows:

12
#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 the expression x is more likely to be true (likely) or less likely to be true (unlikely).

Example:

123
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

123456789101112131415161718192021222324252627282930
// 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_The two header files, file.h, are sufficient; use seq_file is used for simpler printing.

Debug printing

dump_stack()

dump_stack()Its function is:

Print the current CPU’s call stack (call trace) to the kernel log.

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

Example:

12345678
#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)

WARN_ON(condition) function: When the condition in parentheses is true, the kernel throws a stack traceback and prints the function call relationship.

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

WARN_ON actually also calls dump_stack, except that it adds a parameter condition to determine whether the condition is true., for example, WARN_ON(1) means the condition is true, and the function will execute successfully.

Example:

12345678
#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 similar to BUG_ON() statements, which are very much like a kernel runtime assertion, meaning that the BUG_ON() statement,Once BUG_ON() executes, the kernel will immediately throw an oops(only when Linux encounters a fatal error will it throw an oops), causing a stack traceback and printing of error information.

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 is true. For example, BUG_ON(1) means the condition is true, and the function will execute successfully.

123456789
#include <linux/module.h>#include <linux/kernel.h>static int __init helloworld_init(void){	printk(KERN_EMERG "helloworld_init\n");	BUG_ON(1);	return 0;}

panic (fmt…)

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

12345678
#include <linux/module.h>#include <linux/kernel.h>static int __init helloworld_init(void){	printk(KERN_EMERG "helloworld_init\n");	panic("!!!!!!!!!!!!!!!!!!!!!!!!!!!!");	return 0;}
Loading comments…