If you are following our free Linux device drivers course, this lecture is where the pieces finally click together. Before you write your first driver, you need to understand how Linux identifies every piece of hardware on your system – and that identification system is built on device namespace, major numbers, and minor numbers. This concept is one of the most fundamental building blocks in the entire Linux kernel, and it is also part of the foundation for our free Linux kernel development course and free embedded systems course.
By the end of this lecture you will be comfortable reading /proc/devices, understand exactly what a major and minor number pair means, and know why modern kernel developers almost never hardcode these numbers anymore.
What You Will Learn
- How Linux organizes every device into a namespace using block and character categories
- What a major number and a minor number actually represent inside the kernel
- How the 32-bit
dev_tvalue is built internally, and how to decode it yourself - Why static major number allocation is now considered legacy, and how dynamic allocation works
- How the
miscdevice class (major #10) solves the major-number shortage problem - How to inspect major/minor numbers on a live system using simple commands
- A beginner-level overview of the Linux Device Model (LDM) and sysfs
Prerequisites
This lecture assumes you already know:
- Basic C programming
- How to compile and load a simple Linux kernel module (
insmod/rmmod) - Basic Linux command line usage
If you have not covered kernel modules yet, we recommend going through the earlier lectures in this free Linux kernel development course first.
What Is the Linux Device Namespace?
Every device that Linux can talk to – a keyboard, an RTC chip, a USB webcam, a temperature sensor on an I2C bus – is placed inside a single, organized tree called the device namespace. Think of it as a giant filing cabinet: the kernel first decides whether a device belongs to the “block” drawer or the “char” drawer, and inside each drawer, devices are grouped by a major number and further split by a minor number.
This hierarchy has not changed in the modern kernel. What has changed is how devices get their numbers in practice – almost all new drivers today request numbers dynamically instead of picking a fixed value, which we will cover shortly.
Block vs Character Devices
The simplest way to tell the two apart: block devices can be mounted and become part of a filesystem, character devices cannot. A rough rule of thumb – if the hardware is not storage and not a network interface, it is almost certainly a character device.
| Aspect | Block Device | Character Device |
|---|---|---|
| Mountable | Yes | No |
| Data access pattern | Random access, in fixed-size blocks | Usually sequential, byte-by-byte |
| Typical examples | NVMe SSD, eMMC, SD card | RTC, sensors, touchscreen, keyboard |
| Common node prefix | /dev/sdX, /dev/nvme0n1 |
/dev/ttyX, /dev/i2c-X |
Understanding dev_t: The Core of Every Linux Device Driver’s Numbering System
Internally, the kernel does not store the major and minor number as two separate fields. It packs both into a single 32-bit value of type dev_t. Of these 32 bits, the upper 12 bits hold the major number and the lower 20 bits hold the minor number. This gives the kernel room for up to 4,096 major numbers, and each major number can have up to roughly 1 million minor numbers. This encoding has remained the same for a long time and is still exactly how current mainline kernels represent device numbers.
You never need to do this bit-packing by hand. The kernel gives you three macros for it:
MAJOR(dev_t dev) // extract the major number
MINOR(dev_t dev) // extract the minor number
MKDEV(major, minor) // build a dev_t from major and minor
Here is a minimal, original example showing how a driver typically reads back the major and minor number it was assigned:
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/printk.h>
static dev_t ep_devno;
static int __init ep_devno_demo_init(void)
{
ep_devno = MKDEV(240, 0); /* example major, minor pair */
pr_info("epdemo: major = %d, minor = %d\n",
MAJOR(ep_devno), MINOR(ep_devno));
return 0;
}
static void __exit ep_devno_demo_exit(void)
{
pr_info("epdemo: module unloaded\n");
}
module_init(ep_devno_demo_init);
module_exit(ep_devno_demo_exit);
MODULE_LICENSE("GPL");
Static vs Dynamic Major Number Allocation for Linux Device Drivers (Modern Best Practice)
Older Linux documentation – including the classic devices.txt registry once maintained by LANANA (Linux Assigned Names And Numbers Authority) – lists a large, fixed table mapping major numbers to specific device types. That table is still shipped with the kernel source today, but it is effectively a historical record now. The upstream maintainers have stated plainly that new drivers should not go hunting for a free number in that list; they should ask the kernel to hand one out dynamically.
This is genuinely important if you are taking any free Linux device drivers course seriously in 2026: real-world drivers merged into the mainline kernel almost always use alloc_chrdev_region() instead of register_chrdev_region() with a hardcoded number. Here is an original example demonstrating the modern pattern:
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/printk.h>
#define EP_DEVICE_NAME "epdemo"
#define EP_MINOR_COUNT 1
static dev_t ep_dev_num;
static int __init ep_alloc_init(void)
{
int ret;
ret = alloc_chrdev_region(&ep_dev_num, 0, EP_MINOR_COUNT,
EP_DEVICE_NAME);
if (ret < 0) {
pr_err("epdemo: could not allocate major number\n");
return ret;
}
pr_info("epdemo: allocated major %d minor %d\n",
MAJOR(ep_dev_num), MINOR(ep_dev_num));
return 0;
}
static void __exit ep_alloc_exit(void)
{
unregister_chrdev_region(ep_dev_num, EP_MINOR_COUNT);
}
module_init(ep_alloc_init);
module_exit(ep_alloc_exit);
MODULE_LICENSE("GPL");
Once loaded, you can confirm the major number the kernel handed out by checking /proc/devices:
$ sudo insmod ep_alloc_demo.ko
$ cat /proc/devices | grep epdemo
240 epdemo
The misc Device Class: A Shortcut Every Linux Device Driver Beginner Should Know
With only 4,096 character major numbers available and thousands of different device types in the wild, the kernel needed an escape hatch. That escape hatch is the misc class, permanently assigned character major number 10. Instead of every small, simple driver requesting its own major number, it registers itself under major 10 and lets the kernel hand out a unique minor number automatically.
This is the single most important shortcut for anyone starting out in a free embedded systems course or writing their first driver: instead of manually managing alloc_chrdev_region(), cdev_init(), and cdev_add(), you can register a simple device in just a few lines using the misc framework.
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/printk.h>
static int ep_misc_open(struct inode *inode, struct file *filp)
{
pr_info("epmisc: device opened\n");
return 0;
}
static const struct file_operations ep_misc_fops = {
.owner = THIS_MODULE,
.open = ep_misc_open,
};
static struct miscdevice ep_misc_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = "epmisc",
.fops = &ep_misc_fops,
};
static int __init ep_misc_init(void)
{
return misc_register(&ep_misc_device);
}
static void __exit ep_misc_exit(void)
{
misc_deregister(&ep_misc_device);
}
module_init(ep_misc_init);
module_exit(ep_misc_exit);
MODULE_LICENSE("GPL");
After loading this module, the kernel automatically creates /dev/epmisc for you (assuming udev or devtmpfs is active, which it is on virtually every modern distribution). You can verify this immediately:
$ sudo insmod ep_misc_demo.ko
$ ls -l /dev/epmisc
crw------- 1 root root 10, 56 Jul 10 10:15 /dev/epmisc
$ cat /proc/devices | grep misc
10 misc
Notice the major number is always 10, while the minor number (56 in this example) was picked automatically by the kernel. That is the misc class in action: hundreds of unrelated small drivers all sharing one major number, distinguished only by their minor numbers.
How to Inspect Device Numbers on a Running System
You do not need to write any code to explore this concept – your existing Linux machine already has plenty of examples. Try these commands:
# List every registered major number, block and char
$ cat /proc/devices
# See the major:minor pair for a specific device node
$ ls -l /dev/rtc0
crw-rw---- 1 root root 249, 0 Jul 10 09:02 /dev/rtc0
# Ask udev for detailed attributes of a device
$ udevadm info -a -n /dev/rtc0
# See which sysfs entry backs a given device node
$ ls -l /sys/dev/char/ | grep 249
Spending ten minutes with these commands will teach you more than any diagram – it is one of the best free, hands-on exercises available if you are working through a free Linux kernel development course and want the concepts to actually stick.
A Quick Look at the Linux Device Model (LDM) and sysfs
Major and minor numbers tell the kernel which device you mean, but the Linux Device Model (LDM) is what ties devices, buses, and drivers together into one coherent picture. The LDM exposes this picture to user space through the sysfs pseudo-filesystem, normally mounted at /sys.
Broadly, the LDM ties together three things: the buses on the system (USB, I2C, SPI, PCI, platform), the devices attached to those buses, and the drivers that operate those devices. Every device node you create still ultimately links back into this model, which is why /sys/dev/char/<major>:<minor> and /sys/dev/block/<major>:<minor> exist – they are direct, symlinked lookups from a device number straight into its sysfs entry.
Real-World Use Cases
These are not just textbook examples – this is exactly the kind of scenario you will run into once you move past the basics of this free Linux device drivers course and start working with real hardware.
- Embedded sensor drivers: a temperature or IMU driver on an I2C bus almost always uses the misc framework for a simple control interface.
- Watchdog timers: many SoC watchdog drivers register through the misc class rather than owning a dedicated major number.
- Debug and diagnostic interfaces: vendor BSPs frequently expose a small debug device node through
misc_register()for quick userspace access during bring-up. - Storage stacks: block major numbers remain relevant when you look at how NVMe or eMMC controllers expose multiple partitions under one major.
Common Mistakes and Troubleshooting
- Hardcoding a major number you found online: it may already be in use on some systems, causing a silent conflict. Always prefer dynamic allocation.
- Forgetting to call
unregister_chrdev_region()ormisc_deregister(): this leaks the number and can blockrmmod/insmodcycles during development. - Assuming
/dev/<name>is created automatically without udev/devtmpfs: on a minimal embedded root filesystem, you may need to create the node manually withmknod. - Confusing “major identifies the driver” with “major identifies the device”: a single major (like misc, major 10) can represent dozens of completely unrelated drivers.
Best Practices
Keep these habits from day one of your free Linux device drivers course journey, and you will avoid most of the beginner mistakes seen in real driver code reviews.
- Default to dynamic allocation (
alloc_chrdev_region()ormisc_register()withMISC_DYNAMIC_MINOR) unless you have a documented reason to need a fixed number. - Use the misc framework for simple, single-instance character devices – it removes a large amount of boilerplate compared to managing
cdevdirectly. - Always check
/proc/devicesafter loading a new module during development to confirm what number you actually received. - Clean up in the reverse order of registration inside your module’s exit function.
Performance and Security Considerations
Performance: device number lookup itself is O(1) and not a bottleneck; performance concerns in real drivers come from what happens inside your file operations, not from the numbering scheme.
Security: the permissions on a device node (visible in the ls -l output as crw------- etc.) control who can open the device at all. Pair sensible default permissions with a proper udev rule so that sensitive device nodes are not left world-accessible.
Summary / Key Takeaways
- Every Linux device lives inside a namespace split first by type (block/char), then by major number, then by minor number.
dev_tpacks a 12-bit major and a 20-bit minor into one 32-bit value, unpacked withMAJOR()andMINOR().- Static major numbers from the historical
devices.txtlist are largely legacy; dynamic allocation is the modern standard. - The misc class (major 10) lets simple drivers skip major-number management entirely.
- The Linux Device Model and sysfs tie devices, buses, and drivers into one browsable tree under
/sys.
Conclusion
Understanding device namespace and major/minor numbers is not just academic trivia – it is the foundation every character device driver you write will sit on. Once this concept is solid, the rest of driver development (file operations, ioctl, sysfs attributes) becomes far easier to reason about. This lecture is part of our ongoing free Linux device drivers course, and the next lectures will build directly on the misc framework you just learned here, moving into implementing real read/write operations. Keep practicing with the commands shown above on your own machine – hands-on exploration is the fastest way to make this material permanent.
Frequently Asked Questions
Q1. What is the difference between a major number and a minor number in Linux?
The major number identifies the driver (or device class), while the minor number identifies a specific instance managed by that driver.
Q2. Why is the misc device major number always 10?
Major number 10 is a fixed, kernel-reserved value for the miscellaneous device class, allowing many small drivers to share it and be told apart only by their minor number.
Q3. Should I still use static major numbers from devices.txt for a new driver?
No. Modern kernel guidance recommends dynamic allocation for new drivers; the static list is kept mainly for historical/reference purposes.
Q4. How many major numbers can Linux support?
Up to 4,096 for block devices and 4,096 for character devices, since 12 bits are reserved for the major number inside dev_t.
Q5. What happens if I forget to unregister my device number?
The number stays reserved until the module is fully unloaded or the system reboots, which can block you from reloading the same module during development.
Q6. Does devtmpfs replace udev?
Devtmpfs creates basic device nodes automatically at driver registration time; udev (or systemd-udevd) then applies naming rules and permissions on top of that.
Q7. Can two different drivers share the same major number?
Yes – that is exactly what the misc class does, and it is also possible with manually implemented drivers as long as minor numbers do not collide.
Q8. Is this material relevant for embedded Linux, not just desktop Linux?
Yes. Device namespace and the misc framework are used constantly in embedded BSPs for sensors, watchdogs, and other simple peripherals, making this a core topic in any free embedded systems course.
More lectures on character device drivers, the misc framework, and kernel internals are coming up next in this series.
Browse Course Index Join EmbeddedPathashala
2 Comments