If you are searching for a free Linux kernel development course that actually explains how timing works inside the kernel, this lecture is a good place to start. Understanding Linux kernel delay functions is one of the very first skills every device driver author needs, because hardware almost never responds instantly. A sensor needs a settling time, an I2C bus needs a stabilization gap, a GPIO line needs to be held for a few microseconds before it is read back. This is exactly where Linux kernel delay functions come in.
This lecture is part of a free Linux device drivers course and a free embedded systems course published by EmbeddedPathashala, and every example here has been rewritten and re-tested against a modern 6.x kernel tree rather than copied from any older reference.
What You Will Learn
- Why Linux kernel delay functions are split into blocking and non-blocking categories
- How
ndelay(),udelay(), andmdelay()behave internally on modern kernels - Why busy-wait delay functions must never be used for long durations
- How
usleep_range(),msleep(), and the newerfsleep()wrapper improve on older approaches - A simple rule of thumb for picking the right delay API in your own driver
Prerequisites
You should already know what a kernel module is, how to build one with a basic Makefile, and the difference between process context and interrupt (atomic) context. If any of that is unfamiliar, go back one lecture in this free Linux kernel development course before continuing.
Two Families of Linux Kernel Delay Functions
Every delay routine the kernel exposes to module authors falls into one of two families. The first family burns CPU cycles in a tight loop and never lets the scheduler run. The second family actually parks the current task and lets the scheduler pick something else to run until the timeout expires. Picking the wrong family for your context is one of the most common beginner mistakes in kernel programming.
Non-Blocking Linux Kernel Delay Functions
The non-blocking group is declared in <linux/delay.h> and is meant strictly for atomic or interrupt context, where sleeping is illegal. Because these routines spin the CPU instead of sleeping, they should only ever be used for very short waits.
| API | Typical Range | Behaviour |
|---|---|---|
ndelay(ns) | nanoseconds | Busy-waits, never schedules out |
udelay(us) | microseconds | Busy-waits, never schedules out |
mdelay(ms) | milliseconds (should stay small) | Busy-waits, never schedules out |
A simple, original example that could sit inside a GPIO bit-bang routine:
#include <linux/delay.h>
static void ep_toggle_line(void)
{
/* Hold the line high for 20 microseconds before sampling it back */
gpio_set_value(EP_GPIO_LINE, 1);
udelay(20);
gpio_set_value(EP_GPIO_LINE, 0);
}
Important: never call mdelay() for anything beyond a couple of milliseconds. It wastes CPU time that the scheduler could have handed to another task, and on a busy system it can make your driver look like it has hung.
Blocking Linux Kernel Delay Functions
When you are in normal process context — for instance inside a driver’s probe(), open(), or ioctl() callback — you should prefer the blocking family, because it lets the CPU do other useful work while your driver waits.
#include <linux/delay.h>
static int ep_reset_sensor(void)
{
gpio_set_value(EP_RESET_GPIO, 0);
/* Give the hardware a comfortable settling window */
usleep_range(1000, 1500);
gpio_set_value(EP_RESET_GPIO, 1);
msleep(50);
return 0;
}
usleep_range(min, max) is preferred over a plain udelay() replacement for sub-millisecond-to-low-millisecond waits because it gives the kernel’s high-resolution timer subsystem some slack to batch wakeups, which is friendlier to power management. For longer, simple waits, msleep(ms) remains the standard choice. Recent kernel trees also expose fsleep(us), a convenience wrapper that automatically picks between udelay(), usleep_range(), and msleep() based on the duration you pass in, which is handy when the exact wait time is only known at runtime.
Common Mistakes with Linux Kernel Delay Functions
- Calling
msleep()inside a spinlock critical section — this is a bug because you are still in atomic context even though it “looks like” process context. - Using
mdelay()for multi-second waits inside a probe function, which blocks the whole boot sequence. - Forgetting that
udelay()accuracy depends onloops_per_jiffycalibration and can drift on some low-end SoCs. - Reaching for a delay function at all when a proper completion, wait queue, or interrupt would be the correct design.
Best Practices
- Default to blocking sleep APIs unless you have confirmed you are in atomic context.
- Keep busy-wait delays as short as physically possible.
- Prefer
usleep_range()overudelay()whenever sleeping is legal, for better power efficiency. - Document why a delay exists — future maintainers (including you) will thank you.
Performance Considerations
Every microsecond spent busy-waiting is a microsecond the scheduler could have given to another runnable task. On multi-core embedded SoCs this rarely matters for a single short delay, but on single-core boards a careless mdelay() loop in a hot path can visibly stall the whole system, including audio and network stacks.
Key Takeaways
- Linux kernel delay functions split cleanly into non-blocking (
*delay()) and blocking (*sleep()) families. - Non-blocking delay functions are only safe for short waits in atomic or interrupt context.
- Blocking delay functions should be your default choice in process context.
fsleep()is a convenient modern wrapper for runtime-variable delays.
Conclusion
Getting comfortable with Linux kernel delay functions is a small but essential milestone in this free Linux kernel development course. Once you can confidently choose between ndelay(), udelay(), mdelay(), usleep_range(), and msleep(), you are ready to move on to kernel timers, which let you schedule work to happen later without blocking anything at all — the topic of the next lecture in this free Linux device drivers course.
Frequently Asked Questions
They pause execution inside kernel or driver code so that hardware has enough time to respond before the next instruction runs, such as waiting for a sensor reset line or a bus stabilization window.
udelay() busy-waits and never lets the scheduler run, so it is only safe in atomic context for very short waits. usleep_range() actually sleeps the calling task and is only usable in process context.
No. msleep() can put the current task to sleep, and interrupt context has no “current task” to sleep in this sense — doing so will crash or corrupt the kernel.
Because it busy-waits, a long mdelay() call freezes the CPU it runs on, preventing the scheduler from running any other task for that entire duration.
fsleep(us) is a modern convenience wrapper that automatically selects the best underlying delay mechanism based on the requested duration, which is useful when the wait time is only known at runtime rather than compile time.
Yes. This lecture is part of EmbeddedPathashala’s free Linux kernel development course, which also covers free Linux device drivers course material and a free embedded systems course track.
Next up: Linux Kernel Timers — how to schedule work without blocking.
Next Lecture →
2 Comments