1. Why Device Numbers Are Needed
In the previous section, you saw that the /dev directory contains a large number of device files. For example:
ls -l /dev/null /dev/tty0 /dev/mmcblk02
Example output:
crw-rw-rw- 1 root root 1, 3 Dec 9 19:02 /dev/null
crw--w---- 1 root tty 4, 0 Dec 9 19:02 /dev/tty0
brw-rw---- 1 root disk 179, 0 Dec 9 19:02 /dev/mmcblk02
3
There are two important pieces of information:
- The first character of the first column:
cindicates character device,bindicates block device. - The third-from-last and second-from-last columns: for example
1, 3,4, 0,179, 0.
These two numbers combined form the device number (device number):
- The first number is the major number (major number).
- The second number is the minor number (minor number).
When a user-space program executes:
int fd = open("/dev/tty0", O_RDWR);The kernel needs to find the corresponding driver and specific device instance based on /dev/tty0. The device number serves as a bridge here:
- Through the major number, determine which driver is responsible for handling;
- Through the minor number, determine which device instance under that driver.
This can be summarized as:
Device numbers are used to implement the mapping of " device file ↔ driver program ↔ specific device instance ".
2. Major Number and Minor Number
1. Major Number (major number)
The major number is used to identify the driver or device category to which the device belongs.
- Internally, the character device subsystem maintains a table structure indexed by major number.
- Each major number is associated with a certain driver (or subsystem).
- When the kernel parses the device number of
/dev/xxx, it finds the corresponding driver's entry based on the major number.
For example (taking /proc/devices as an example):
cat /proc/devicesOutput (excerpt):
lckfb@linaro-alip:~$ cat /proc/devices
Character devices:
1 mem
4 /dev/vc/0
4 tty
4 ttyS
5 /dev/tty
5 /dev/console
5 /dev/ptmx
7 vcs
10 misc
13 input
81 video4linux
89 i2c
90 mtd
.......................2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Explanation:
- Character device major number
4corresponds tottyclass devices. - Block device major number
13corresponds toinputdevices (mouse/keyboard).
The range and allocation policy of major numbers are maintained by the kernel. Some major numbers have convention uses in documentation, and other ranges are available for dynamic allocation to various drivers.
2. Minor Number (minor number)
The minor number is used to identify different device instances under the same major number.
- A driver (corresponding to a certain major number) can manage multiple devices.
- Each device has a different minor number for distinction.
For example, suppose a character device driver uses major number 240 and manages two instances:
/dev/mychar0: Major number 240, minor number 0 →<240, 0>/dev/mychar1: Major number 240, minor number 1 →<240, 1>
When processing operations like open / read / write, the driver can select the corresponding data structure or hardware resource based on the minor number.
Let's continue to look at the tty serial port example:
lckfb@linaro-alip:~$ ls -l /dev/tty*
crw-rw-rw- 1 root tty 5, 0 Dec 9 19:02 /dev/tty
crw--w---- 1 root tty 4, 0 Dec 9 19:02 /dev/tty0
crw--w---- 1 root tty 4, 1 Dec 9 19:02 /dev/tty1
crw--w---- 1 root tty 4, 10 Dec 9 19:02 /dev/tty10
crw--w---- 1 root tty 4, 11 Dec 9 19:02 /dev/tty11
crw--w---- 1 root tty 4, 12 Dec 9 19:02 /dev/tty12
crw--w---- 1 root tty 4, 13 Dec 9 19:02 /dev/tty13
crw--w---- 1 root tty 4, 14 Dec 9 19:02 /dev/tty14
crw--w---- 1 root tty 4, 15 Dec 9 19:02 /dev/tty15
........2
3
4
5
6
7
8
9
10
11
We have so many devices of the same serial port type, possibly dozens or hundreds. Combining major and minor numbers can quickly locate:
- Major number determines the device type as serial port device
- Minor number determines which device under the serial port device
tty10:
- Major number is
4- Minor number is
10
tty11:
- Major number is
4- Minor number is
11
tty12:
- Major number is
4- Minor number is
12……
3. Data Type and Layout of Device Numbers
In the kernel, device numbers are represented using the dev_t type. On most architectures, it is a 32-bit unsigned integer with a bit layout roughly as follows:
- High 12 bits: major number
- Low 20 bits: minor number
That is:
- Major number theoretical range: 0 ~ 2¹² - 1 (0 ~ 4095)
- Minor number theoretical range: 0 ~ 2²⁰ - 1 (0 ~ 1,048,575)
The kernel provides a set of macros for manipulating dev_t:
MAJOR(dev_t dev); // Get major number from dev
MINOR(dev_t dev); // Get minor number from dev
MKDEV(unsigned int major, unsigned int minor); // Construct dev_t2
3
Typical usage example:
dev_t dev;
/* Construct a dev_t from major number 240, minor number 0 */
dev = MKDEV(240, 0);
/* Extract major and minor numbers */
unsigned int ma = MAJOR(dev);
unsigned int mi = MINOR(dev);2
3
4
5
6
7
8
In character device drivers, you typically define:
static dev_t dev_num; // Complete device number
static int major; // Major number
static int minor = 0; // Starting minor number2
3
4. Two Types of Device Number Allocation
When writing a character device driver, the driver needs to register the device number range it wants to use with the kernel. This process is separate from subsequent cdev registration and /dev node creation. This section only discusses the registration and release of the device number itself.
The kernel provides two commonly used methods:
- Static registration (specify major number):
register_chrdev_region - Dynamic allocation (automatically allocate major number):
alloc_chrdev_region
1. Static Registration
The driver can directly specify the desired major number and starting minor number, then register with the kernel:
static dev_t dev_num;
static int major = 240; /* Desired major number 240 */
static int minor = 0; /* Starting minor number */
static int __init mydrv_init(void)
{
int ret;
dev_num = MKDEV(major, minor);
/* Register starting from dev_num, 1 consecutive device number */
ret = register_chrdev_region(dev_num, 1, "mychar_static");
if (ret < 0) {
pr_err("register_chrdev_region failed\n");
return ret;
}
pr_info("mychar_static: registered with major=%d, minor=%d\n",
MAJOR(dev_num), MINOR(dev_num));
return 0;
}
static void __exit mydrv_exit(void)
{
unregister_chrdev_region(dev_num, 1);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Explanation:
register_chrdev_region(dev_t from, unsigned count, const char *name);from: Starting device number (includes major and minor numbers).count: Number of consecutive device numbers.name: Name, used for display in/proc/devicesand other locations.
- For character devices, the major number must not already be in use by another driver.
Applicable scenarios:
- Some legacy systems or specific application scenarios that require a fixed major number.
Limitations:
- The developer needs to manually select a major number and ensure it does not conflict with existing devices in the system.
- On different kernel versions or platforms, this major number may already be occupied.
For generally newly developed drivers, dynamic allocation is more recommended.
2. Dynamic Allocation
In dynamic allocation, the kernel selects an unused major number. The driver only needs to specify:
- Starting minor number;
- Number of consecutive devices needed;
- Name.
static dev_t dev_num;
static int major;
static int minor = 0;
static int __init mydrv_init(void)
{
int ret;
/* Request dynamic allocation of 1 device number, starting from minor number 0 */
ret = alloc_chrdev_region(&dev_num, minor, 1, "mychar_dynamic");
if (ret < 0) {
pr_err("alloc_chrdev_region failed\n");
return ret;
}
major = MAJOR(dev_num);
minor = MINOR(dev_num);
pr_info("mychar_dynamic: registered with major=%d, minor=%d\n",
major, minor);
return 0;
}
static void __exit mydrv_exit(void)
{
unregister_chrdev_region(dev_num, 1);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Explanation:
alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);dev: Returned startingdev_t.baseminor: Starting minor number.count: Starting frombaseminor, how many consecutive minor numbers to apply for.name: Device name identifier.
Advantages:
- No need to manually manage major number allocation.
- Less likely to cause conflicts across platforms and kernel versions.
- This is the recommended approach for new driver development.