设备号是在驱动module中分配并注册的,驱动module拥有这个设备号,而/dev目录下的设备文件是根据这个设备号创建的,
当访问/dev目录下的设备文件时,驱动module就知道,自己该出场服务了(当然是由内核通知)。
在Linux内核看来,主设备号标识设备对应的驱动程序,告诉Linux内核使用哪一个驱动程序为该设备(也就是/dev下的设备文件)服务;
而次设备号则用来标识具体且唯一的某个设备。
一个字符设备或者块设备都有一个主设备号和次设备号。主设备号和次设备号统称为设备号。主设备号用来表示一个特定的驱动程序。次设备号用来表示使用该驱动程序的各设备。例如一个嵌入式系统,有两个LED指示灯,LED灯需要独立的打开或者关闭。那么,可以写一个LED灯的字符设备驱动程序,可以将其主设备号注册成5号设备,次设备号分别为1和2。这里,次设备号就分别表示两个LED灯。
设备文件通常都在 /dev 目录下。如:
beyes@linux-beyes:~/C/kernel/memory> ll /dev |more
总计 0
crw-rw—- 1 root uucp 4, 70 04-14 18:16 ttyS6
crw-rw—- 1 root uucp 4, 71 04-14 18:16 ttyS7
crw-rw—- 1 root tty 7, 0 08-08 18:58 vcs
crw-rw—- 1 root tty 7, 1 08-08 18:58 vcs1
crw-rw-rw- 1 root root 1, 7 08-08 18:58 full
crw-rw-rw- 1 root root 1, 3 04-14 18:16 null
如上,前面第一个字符为c 的表示字符设备。在字符设备里,有主设备号和次设备号。如上1,4,7 分别是主设备号,0,1,3,7,70,71都是次设备号。一般的,主设备号标识出与设备关联的设备驱动。如 /dev/null 和 /dev/full 由 1 号驱动来管理,/dev/vcs 和/dev/vcs1由 7 号驱动来管理,/dev/ttyS6 由 4 号驱动来管理。
现在的 Linux 内核允许多个驱动共享一个主设备号,但更多的设备都遵循一个驱动对一个主设备号的原则。
内核由次设备号确定当前所指向的是哪个设备。根据所编写的驱动程序,可以从内核那里得到一个直接指向设备的指针,或者使用次设备号作为一个设备本地数组的索引。但不论如何,内核自身几乎不知道次设备号的什么事情。
对于Linux系统中,一般字符设备和驱动之间的函数调用关系如下图所示
上图描述了用户空间应用程序通过系统调用来调用程序的过程。一般而言在驱动程序的设计中,会关系 struct file 和 struct inode 这两个结构体。
用户空间使用open()系统调用函数打开一个字符设备时( int fd = open(“dev/demo”, O_RDWR) )大致有以下过程:
1、在虚拟文件系统VFS中的查找对应与字符设备对应 struct inode节点
2、遍历字符设备列表(chardevs数组),根据inod节点中的 cdev_t设备号找到cdev对象
3、创建struct file对象(系统采用一个数组来管理一个进程中的多个被打开的设备,每个文件秒速符作为数组下标标识了一个设备对象)
4、初始化struct file对象,将 struct file对象中的 file_operations成员指向 struct cdev对象中的 file_operations成员(file->fops = cdev->fops)
5、回调file->fops->open函数
在文件(sound/core/sound.c)中,ALSA定义了一个字符设备(CONFIG_SND_MAJOR =116),所有AUDIO设备,都是该设备的字设备:
snd_open接口比较重要,snd_open接口通过文件节点inode得到了次设备号,通过snd_minors数组得到对应的声音逻辑设备的文件操作,
调用对应的open接口,并调用fops_put替换成了对应的逻辑设备的文件操作(snd_minors里维护).
//文件提供了处理设备号相关的宏:
MKDEV(major, minor) // 设备的dev_t
MAJOR(dev_t) // 主设备号
MINOR(dev_t) // 次设备号
//需要注意的是这三个宏在在内核和用户态有不同的解释,下面截取kdev_t.h的相关片段:
#ifndef _LINUX_KDEV_T_H
#define _LINUX_KDEV_T_H
#ifdef __KERNEL__
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
//...........................................
#else /* __KERNEL__ */
/*
Some programs want their definitions of MAJOR and MINOR and MKDEV
from the kernel sources. These must be the externally visible ones.
*/
#define MAJOR(dev) ((dev)>>8)
#define MINOR(dev) ((dev) & 0xff)
#define MKDEV(ma,mi) ((ma)<<8 | (mi))
#endif /* __KERNEL__ */
#endif
//可以看到如果定义了__KERNEL__宏,主次设备号占据的位数不同。
static const struct file_operations snd_fops =
{
.owner = THIS_MODULE,
.open = snd_open,
.llseek = noop_llseek,
};
static int snd_open(struct inode *inode, struct file *file)
{
unsigned int minor = iminor(inode);
struct snd_minor *mptr = NULL;
const struct file_operations *new_fops;
int err = 0;
mptr = snd_minors[minor];//通过minor取得从设备
new_fops = fops_get(mptr->f_ops);
replace_fops(file, new_fops);
if (file->f_op->open)
err = file->f_op->open(inode, file);
return err;
}
static inline unsigned iminor(const struct inode *inode)
{
return MINOR(inode->i_rdev);
}
//注册alsa主设备,
static int major = CONFIG_SND_MAJOR;
static int __init alsa_sound_init(void)
{
snd_major = major;
snd_ecards_limit = cards_limit;
if (register_chrdev(major, "alsa", &snd_fops)) {//注册声卡主设备,major为主设备号
pr_err("ALSA core: unable to register native major device number %d\n", major);
return -EIO;
}
if (snd_info_init() < 0) {
unregister_chrdev(major, "alsa");
return -ENOMEM;
}
snd_info_minor_register();
return 0;
}
register_chrdev( )申请指定的设备号,并且将其注册到字符设备驱动模型中.
它所做的事情为:
1、注册设备号, 通过调用 __register_chrdev_region() 来实现
2、分配一个cdev, 通过调用 cdev_alloc() 来实现
3、将cdev添加到驱动模型中, 这一步将设备号和驱动关联了起来. 通过调用 cdev_add() 来实现
4、将第一步中创建的 struct char_device_struct 对象的 cdev 指向第二步中分配的cdev. 由于register_chrdev()是老的接口,这一步在新的接口中并不需要。
#define CHRDEV_MAJOR_HASH_SIZE 255
static DEFINE_MUTEX(chrdevs_lock);
static struct char_device_struct {
struct char_device_struct *next; // 结构体指针
unsigned int major; // 主设备号
unsigned int baseminor; // 次设备起始号
int minorct; // 次备号个数
char name[64];
struct cdev *cdev; /* will die */
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];// 只能挂255个字符主设备
#include
int register_chardev (unsigned int major, const char *name, struct file_operations *fops)
说明:register_chrdev 是注册设备驱动程序的内核函数。
参数:
major 主设备号,该值为 0 时,自动运行分配。而实际值不是 0 。
name 设备名称;
fops file_operations 结构体变量地址(指针)。
返回值:
major 值为 0 ,正常注册后,返回分配的主设备号。如果分配失败,返回 EBUSY 的负值 ( -EBUSY ) 。
major 值若大于 linux/major.h (2.4内核)中声明的最大值 (#define MAX_CHRDEV 255) ,则返回EINVAL 的负值 (-EINVAL) 。
指定 major 值后,若有注册的设备,返回 EBUSY 的负值 (-EBUSY)。若正常注册,则返回 0 值。
register_chrdev
__register_chrdev(major, 0, 256, name, fops);
struct char_device_struct *cd;
cd = __register_chrdev_region(major, baseminor, count, name);
cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
//如果主设备号是0的话,就会从 char_device_struct数组中选择一个没有分配的char_device_struct结构体
//如果找到的话,就以其下标作为主设备号,否则出错返回
cd->major = major;
cd->baseminor = baseminor;//第一个次设备号
cd->minorct = minorct; //此设备号的个数
strlcpy(cd->name, name, sizeof(cd->name));//设置名字
i = major_to_index(major);
return major % CHRDEV_MAJOR_HASH_SIZE;//只有当指定的主设备号大于255时,i才会不等于major
//下面的工作比较难懂,可以参考注释1
cdev = cdev_alloc(); //分配字符设备结构体
cdev->owner = fops->owner; //设置字符设备结构体
cdev->ops = fops;
kobject_set_name(&cdev->kobj, "%s", name);
//向上注册,这里用到了cd->major和baseminor
err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);
Android N:/ # cat proc/devices
Character devices:
1 mem
2 pty
3 ttyp
5 /dev/tty
5 /dev/console
5 /dev/ptmx
10 misc
13 input
14 sound
29 fb
90 mtd
108 ppp
116 alsa //alsa主设备
128 ptm
136 pts
153 mtk_wmt_wifi_chrdev
Block devices:
259 blkext
7 loop
8 sd
31 mtdblock
65 sd
static int snd_pcm_dev_register(struct snd_device *device)
{
err = snd_register_device_for_dev(devtype, pcm->card,
pcm->device,
&snd_pcm_f_ops[cidx],
pcm, str, dev);
}
int snd_register_device_for_dev(int type, struct snd_card *card, int dev,
const struct file_operations *f_ops,
void *private_data,
const char *name, struct device *device)
{
int minor;
struct snd_minor *preg;
preg = kmalloc(sizeof *preg, GFP_KERNEL);
preg->type = type;//playback or capture
preg->card = card ? card->number : -1;
preg->device = dev;
preg->f_ops = f_ops;//文件操作函数,用来操作/dev/snd/pcmCxxDxxp和/dev/snd/pcmCxxDxxc
preg->private_data = private_data;//保存pcm对象
preg->card_ptr = card;
minor = snd_kernel_minor(type, card, dev);
snd_minors[minor] = preg;//使用全局变量保存pcm设备的上下文
//创建pcm的设备节点/dev/snd/pcmCxxDxxp和/dev/snd/pcmCxxDxxc,其中major为主设备号,minor为从设备号
preg->dev = device_create(sound_class, device, MKDEV(major, minor), private_data, "%s", name);
return 0;
}
static int snd_kernel_minor(int type, struct snd_card *card, int dev)
{
int minor;
switch (type) {
case SNDRV_DEVICE_TYPE_SEQUENCER:
case SNDRV_DEVICE_TYPE_TIMER:
minor = type;
break;
case SNDRV_DEVICE_TYPE_CONTROL:
if (snd_BUG_ON(!card))
return -EINVAL;
minor = SNDRV_MINOR(card->number, type);
break;
case SNDRV_DEVICE_TYPE_HWDEP:
case SNDRV_DEVICE_TYPE_RAWMIDI:
case SNDRV_DEVICE_TYPE_PCM_PLAYBACK:
case SNDRV_DEVICE_TYPE_PCM_CAPTURE:
case SNDRV_DEVICE_TYPE_COMPRESS:
if (snd_BUG_ON(!card))
return -EINVAL;
minor = SNDRV_MINOR(card->number, type + dev);//生成minor,从设备号
break;
default:
return -EINVAL;
}
if (snd_BUG_ON(minor < 0 || minor >= SNDRV_OS_MINORS))
return -EINVAL;
return minor;
}
#define SNDRV_MINOR(card, dev) (((card) << 5) | (dev))
Android N:/dev/snd # ls -l
total 0
crw-rw---- 1 system audio 14, 12 2017-08-29 09:59 adsp
crw-rw---- 1 system audio 14, 4 2017-08-29 09:59 audio
crw-rw---- 1 system audio 116, 40 2017-08-29 09:59 comprC0D23
crw-rw---- 1 system audio 116, 2 2017-08-29 09:59 controlC0
crw-rw---- 1 system audio 14, 3 2017-08-29 09:59 dsp
crw-rw---- 1 system audio 14, 0 2017-08-29 09:59 mixer
crw-rw---- 1 system audio 116, 3 2017-08-29 09:59 pcmC0D0p
crw-rw---- 1 system audio 116, 19 2017-08-29 09:59 pcmC0D10p
crw-rw---- 1 system audio 116, 20 2017-08-29 09:59 pcmC0D11p
crw-rw---- 1 system audio 116, 21 2017-08-29 09:59 pcmC0D12c
crw-rw---- 1 system audio 116, 22 2017-08-29 09:59 pcmC0D13c
crw-rw---- 1 system audio 116, 23 2017-08-29 09:59 pcmC0D14p
crw-rw---- 1 system audio 116, 24 2017-08-29 09:59 pcmC0D15c
crw-rw---- 1 system audio 116, 25 2017-08-29 09:59 pcmC0D16c
crw-rw---- 1 system audio 116, 27 2017-08-29 09:59 pcmC0D17c
crw-rw---- 1 system audio 116, 26 2017-08-29 09:59 pcmC0D17p
crw-rw---- 1 system audio 116, 28 2017-08-29 09:59 pcmC0D18p
crw-rw---- 1 system audio 116, 29 2017-08-29 09:59 pcmC0D19p
crw-rw---- 1 system audio 116, 4 2017-08-29 09:59 pcmC0D1c
crw-rw---- 1 system audio 116, 30 2017-08-29 09:59 pcmC0D20p
crw-rw---- 1 system audio 116, 31 2017-08-29 09:59 pcmC0D21p
crw-rw---- 1 system audio 116, 34 2017-08-29 09:59 pcmC0D22c
crw-rw---- 1 system audio 116, 35 2017-08-29 09:59 pcmC0D24p
crw-rw---- 1 system audio 116, 37 2017-08-29 09:59 pcmC0D25c
crw-rw---- 1 system audio 116, 36 2017-08-29 09:59 pcmC0D25p
crw-rw---- 1 system audio 116, 39 2017-08-29 09:59 pcmC0D26c
crw-rw---- 1 system audio 116, 38 2017-08-29 09:59 pcmC0D26p
crw-rw---- 1 system audio 116, 6 2017-08-29 09:59 pcmC0D2c
crw-rw---- 1 system audio 116, 5 2017-08-29 09:59 pcmC0D2p
crw-rw---- 1 system audio 116, 8 2017-08-29 09:59 pcmC0D3c
crw-rw---- 1 system audio 116, 7 2017-08-29 09:59 pcmC0D3p
crw-rw---- 1 system audio 116, 10 2017-08-29 09:59 pcmC0D4c
crw-rw---- 1 system audio 116, 9 2017-08-29 09:59 pcmC0D4p
crw-rw---- 1 system audio 116, 12 2017-08-29 09:59 pcmC0D5c
crw-rw---- 1 system audio 116, 11 2017-08-29 09:59 pcmC0D5p
crw-rw---- 1 system audio 116, 14 2017-08-29 09:59 pcmC0D6c
crw-rw---- 1 system audio 116, 13 2017-08-29 09:59 pcmC0D6p
crw-rw---- 1 system audio 116, 16 2017-08-29 09:59 pcmC0D7c
crw-rw---- 1 system audio 116, 15 2017-08-29 09:59 pcmC0D7p
crw-rw---- 1 system audio 116, 17 2017-08-29 09:59 pcmC0D8p
crw-rw---- 1 system audio 116, 18 2017-08-29 09:59 pcmC0D9c
crw-rw---- 1 system audio 116, 1 2017-08-29 09:59 seq
crw-rw---- 1 system audio 14, 1 2017-08-29 09:59 sequencer
crw-rw---- 1 system audio 14, 8 2017-08-29 09:59 sequencer2
crw-rw---- 1 system audio 116, 33 2017-08-29 09:59 timer
写的一个字符设备驱动为例,在驱动初始化的代码里调用class_create为该设备创建一个class,
再为每个设备调用 device_create创建对应的设备,大致用法如下:
struct class *myclass = class_create(THIS_MODULE, “my_device_driver”);
device_create(myclass, NULL, MKDEV(major_num, 0), NULL, “my_device”);
这样的module被加载时,就会自动在/dev下创建my_device设备文件。
//第一个参数指定类的所有者是哪个模块,第二个参数指定类名。
struct class *class_create(struct module *owner, const char *name)
{
struct class *cls;
int retval;
cls = kzalloc(sizeof(*cls), GFP_KERNEL);
if (!cls) {
retval = -ENOMEM;
goto error;
}
cls->name = name;
cls->owner = owner;
cls->class_release = class_create_release;
retval = class_register(cls);
if (retval)
goto error;
return cls;
error:
kfree(cls);
return ERR_PTR(retval);
}
//第一个参数指定所要创建的设备所从属的类,
//第二个参数是这个设备的父设备,如果没有就指定为NULL,
//第三个参数是设备号,
//第四个参数是设备名称,
//第五个参数是从设备号。
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, const char *fmt, ...)
{
va_list vargs;
struct device *dev;
va_start(vargs, fmt);
dev = device_create_vargs(class, parent, devt, NULL, fmt, vargs);
va_end(vargs);
return dev;
}
以创建pcm设备节点为例:
//注册主设备alsa,设置声卡的回调函数
static int __init alsa_sound_init(void)
{
snd_major = major;
snd_ecards_limit = cards_limit;
if (register_chrdev(major, "alsa", &snd_fops)) {
pr_err("ALSA core: unable to register native major device number %d\n", major);
return -EIO;
}
return 0;
}
//创建类名为sound的sound_class
static int __init init_soundcore(void)
{
int rc;
rc = init_oss_soundcore();
sound_class = class_create(THIS_MODULE, "sound");
sound_class->devnode = sound_devnode;
return 0;
}
//创建pcm的设备节点/dev/snd/pcmCxxDxxp和/dev/snd/pcmCxxDxxc
int snd_register_device_for_dev(int type, struct snd_card *card, int dev,
const struct file_operations *f_ops,
void *private_data,
const char *name, struct device *device)
{
int minor;
struct snd_minor *preg;
preg = kmalloc(sizeof *preg, GFP_KERNEL);
minor = snd_kernel_minor(type, card, dev);
snd_minors[minor] = preg;//使用全局变量保存pcm设备的上下文
preg->dev = device_create(sound_class, device, MKDEV(major, minor), private_data, "%s", name);
return 0;
}
Android N:/sys/class/sound # ls -l
total 0
lrwxrwxrwx 1 root root 0 2017-09-01 15:44 adsp -> ../../devices/platform/soc-audio/sound/card0/adsp
lrwxrwxrwx 1 root root 0 2017-09-01 15:44 audio -> ../../devices/platform/soc-audio/sound/card0/audio
lrwxrwxrwx 1 root root 0 2017-09-01 15:44 card0 -> ../../devices/platform/soc-audio/sound/card0
lrwxrwxrwx 1 root root 0 2017-09-01 15:44 comprC0D23 -> ../../devices/platform/soc-audio/sound/card0/comprC0D23
lrwxrwxrwx 1 root root 0 2017-09-01 15:44 controlC0 -> ../../devices/platform/soc-audio/sound/card0/controlC0
lrwxrwxrwx 1 root root 0 2017-09-01 15:44 dsp -> ../../devices/platform/soc-audio/sound/card0/dsp
lrwxrwxrwx 1 root root 0 2017-09-01 15:44 mixer -> ../../devices/platform/soc-audio/sound/card0/mixer
lrwxrwxrwx 1 root root 0 2017-09-01 15:44 pcmC0D0p -> ../../devices/platform/soc-audio/sound/card0/pcmC0D0p
lrwxrwxrwx 1 root root 0 2017-09-01 15:44 pcmC0D10p -> ../../devices/platform/soc-audio/sound/card0/pcmC0D10p
lrwxrwxrwx 1 root root 0 2017-09-01 15:44 pcmC0D11p -> ../../devices/platform/soc-audio/sound/card0/pcmC0D11p
lrwxrwxrwx 1 root root 0 2017-09-01 15:44 pcmC0D12c -> ../../devices/platform/soc-audio/sound/card0/pcmC0D12c
lrwxrwxrwx 1 root root 0 2017-09-01 15:44 pcmC0D13c -> ../../devices/platform/soc-audio/sound/card0/pcmC0D13c
lrwxrwxrwx 1 root root 0 2017-09-01 15:44 pcmC0D14p -> ../../devices/platform/soc-audio/sound/card0/pcmC0D14p
参考文档
http://www.cnblogs.com/chen-farsight/p/6177870.html