linux设备模型____宏观印象
最近一个机会需要研究一个marvell芯片的设备的驱动,涉及驱动和一些用户态相关部分,正好学习一下驱动和sysfs,本文先是原理,后面的文章是详细描述。本文依托的是linux 2.6.32。
事实上,linux设备模型这个东西,也就是常看到的总线、设备、驱动、类等等,是我们自己抽象出来的理论;但linux的设备驱动,还确实是按照这个理论组建的,不论是linux内核启动过程中的相关初始化过程,还是后续我们自己的驱动的加载删除,都是按照这个理论组建的(也无法不按这种理论组建,因为驱动编程必须得调用内核提供的相应函数),这个组建也是比较繁杂的,后面会详细描述。
另一方面,从代码角度,这个组建过程是很细致的一个过程,它的细致还体现在两方面,一方面,针对总线、设备、驱动、类等等有一堆函数涉及一堆逻辑;另一方面这些总线、设备、驱动、类等等,之所以相关性这么强,还因为linux内核制造出诸如kobject、kset等多种结构来把这个相关性实现,这些东西也很繁杂,后面也会详细描述。
鉴于它很麻烦,所以一点一点描述,首先是从设备模型这个顶层来讲:
肯定都知道sysfs挂在/sys目录下,也肯定知道这个sysfs是和设备驱动关系非常大,到底sysfs是怎么回事后面再说,目前只要知道sysfs和proc很相似、都是基于内存的文件系统、都有查看和设置内核参数的功能、并且sysfs还是linux统一的设备模型的管理者、sysfs挂在/sys目录下即可;先从设备看/sys目录下都是些什么,下面是我的设备的情况:
/ # ls sys/ -l
total 0
drwxr-xr-x 33 root root 0 Jan 1 00:00 block
drwxr-xr-x 11 root root 0 Jan 1 00:00 bus
drwxr-xr-x 28 root root 0 Jan 1 00:00 class
drwxr-xr-x 4 root root 0 Jan 1 00:02 dev
drwxr-xr-x 6 root root 0 Jan 1 00:00 devices
drwxr-xr-x 2 root root 0 Jan 1 00:03 firmware
drwxr-xr-x 2 root root 0 Jan 1 00:03 fs
drwxr-xr-x 3 root root 0 Jan 1 00:03 kernel
drwxr-xr-x 41 root root 0 Jan 1 00:03 module
可见是一堆目录,先把firmware、fs、kernel抛开,其余的block、bus、class、dev、devices、module就是设备当前的设备模型的实现结果;其中block是所有块设备的“集合”,bus是系统当前所有总线的“集合”,class是系统当前都设备的类的“集合”,dev暂时先不管,devices是系统当前所有设备的“集合”,module是系统当前具有非0属性的模块;有时候某些linux机器还会有power目录,这是和电源管理相关的,我这个设备没有,下面是稍细致一点描述这些目录:
/sys/devices:内核对系统中所有设备的分层次表达模型;
/sys/dev/:这个目录下维护一个按字符设备和块设备的主次号码(major:minor)链接到真实的设备(/sys/devices下)的符号链接文件;
/sys/bus/:内核设备按总线类型分层放置的目录结构, devices目录下的所有设备都是连接于某个总线之下,在这里的每一种具体总线之下可以找到每一个具体设备的符号链接,bus是构成 Linux 统一设备模型的重要部分;
/sys/class/:是从一个“类别”的角度区分设备,如从PCI、USB等角度制造几个类别,注意,class目录下的设备不一定挂在任何一个总线上,挂在某个总线上的设备不一定出现在class目录下;
/sys/block/:这里是系统中当前所有的块设备所在;
/sys/firmware/:这里是系统加载固件机制的对用户空间的接口,暂不考虑;
/sys/fs/:这里按照设计是用于描述系统中所有文件系统,包括文件系统本身和按文件系统分类存放的已挂载点,但目前只有 fuse,gfs2 等少数文件系统支持 sysfs 接口,一些传统的虚拟文件系统(VFS)层次控制参数仍然在 sysctl (/proc/sys/fs) 接口中,比如我的设备,fs目录是个空目录;
/sys/kernel/:这里是内核所有可调整参数的位置,目前只有 uevent_helper, kexec_loaded, mm, 和新式的 slab 分配器等几项较新的设计在使用它,其它内核可调整参数仍然位于 sysctl (/proc/sys/kernel) 接口中,比如我的设备,kernel目录是个空目录;
/sys/module/:这里有系统中所有模块的信息,不论这些模块是以内联(inlined)方式编译到内核映像文件(vmlinuz)中还是编译为外部模块(ko文件),都可能会出现在 /sys/module 中: 编译为外部模块(ko文件)在加载后会出现对应的 /sys/module/
/sys/power/:这里是系统中电源选项,这个目录下有几个属性文件可以用于控制整个机器的电源状态,如可以向其中写入控制命令让机器关机、重启等;
除了上面的这些个,目前的内核可能还有一些其他的目录,但目前不用考虑这么多了,目前只考虑对于理解linux设备模型和sysfs最重要的几个,就是上面黑体的4个,bus、device、driver、class;
1、 为什么要搞linux设备模型?
这个确实还是有原因的,正如很多文章的例子所讲,一个U盘插在电脑的USB总线设备上,USB总线设备以PCI设备的身份挂在PCI总线设备上,PCI总线设备直接受CPU管理,当挂起(suspend)电源时,应该以“U盘->USB总线设备->PCI总线设备”的顺序通知每一个设备将电源挂起,当恢复(resume)电源时,应该以反方向的顺序恢复电源,如果顺序不对,设备将无法正常操作;这就是搞linux设备模型的需求或原因。
所以,linux的设备模型要搞成这么个样子:最高层次是总线,可以有不同的总线;然后是每个总线下挂的设备(或是下一级总线,如PCI和USB的关系);而驱动(driver)也挂在总线之下,当它匹配某设备时,也会和该设备产生一些关系;最后是class,它和前面的还不太一样,它是按设备的具体功能分类,底下的各个类和类下边的设备不一定和bus下的设备能够对应,反之亦然;
上面的描述肯定不清楚,这是因为linux设备模型的具体实现是一个非常繁杂的系统,不同部分的代码关联度很大,这对代码效率提高非常有帮助,因为避免了重复地搞很多东西,但确实不利于我这样的初学者理解清楚,下面就是具体描述:
2、 表面理解下什么是bus、device、driver:
下面是linux内核初始化时的一部分内容:
void __init driver_init(void)
{
/* These are the core pieces */
/*devtmpfs 的功用是在 Linux 核心 启动早期建立一个初步的 /dev ,和本文关系不大*/
devtmpfs_init();
/*重要,在/sys目录下创建一个目录叫devices(实际可不单单创建个目录),之后通过总线挂载的设备都会出现在这个目录下的各个子目录里*/
devices_init();
/*重要,在/sys目录下创建一个目录叫bus(同样不单单创建个目录),之后创建的新总线和挂载在某总线下的设备都会出现在这个目录下的各个子目录里*/
buses_init();
/*重要,在/sys目录下创建一个目录叫class(同样不单单创建个目录),之后创建的新class和挂载在某class下的设备都会出现在这个目录下的各个子目录里*/
classes_init();
/*意思差不多,暂不关注*/
firmware_init();
/*虚拟机相关,不关注*/
hypervisor_init();
/*分水岭,至此,/sys下已经有了三个重要的目录(不单单是目录),bus、devices、class*/
/* These are also core pieces, but must come after the
* core core pieces.
*/
/*重要,这可能是内核的第一个总线,platform,它也是个目录在bus目录下*/
platform_bus_init();
/*这可能是内核的第一个设备,system,是/sys/devices目录下第一个添加的东西,它同时是个“集合”,即它本身虽然是设备,但更重要的是它目录底下的设备,暂不关注*/
system_bus_init();
/*后面两个暂不关注*/
cpu_dev_init();
memory_dev_init();
}
小节:
函数buses_init创建了总线bus这么个东西,并且创建了第一个总线platform,并且platform目录下还有device和driver两个目录;先不看函数buses_init里面到底干什么,先记住buses_init这个函数让linux内核有了总线这个概念了,并且有一个platform总线;
函数devices_init创建了设备devices这么个东西,并且创建了第一个设备system;先不看函数devices_init里面到底干什么,先记住devices_init这个函数让linux内核有了设备这个概念了,并且有system这个设备;
函数classes_init创建了类class这么个东西,先不看函数classes_init里面到底干什么,先记住classes_init这个函数让linux内核有了类这个概念了;
从这以后,编译进内核的各个设备将分别在这些目录下增加各自的内容,当系统启动差不多了,再查看/sys目录下,比如我的设备的状态是这样的:
/ # ls sys/bus/ -l
total 0
drwxr-xr-x 4 root root 0 Jan 1 16:59 hid
drwxr-xr-x 4 root root 0 Jan 1 00:00 i2c
drwxr-xr-x 4 root root 0 Jan 1 16:59 mmc
drwxr-xr-x 5 root root 0 Jan 1 16:59 pci
drwxr-xr-x 4 root root 0 Jan 1 13:37 platform
drwxr-xr-x 4 root root 0 Jan 1 16:59 scsi
drwxr-xr-x 4 root root 0 Jan 1 16:59 sdio
drwxr-xr-x 4 root root 0 Jan 1 16:59 usb
drwxr-xr-x 4 root root 0 Jan 1 16:59 usb-serial
/ #
这说明,我的设备现在有一共9个总线;
其中,platform总线里边的device目录是这样的:
/ # ls sys/bus/platform/devices/
boardEnv/ mv64xxx_i2c.0/ regulatory.0/
cust/ mv88fx_neta.0/ serial8250.0/
ehci_marvell.70059/ mvsdio/ serial8250/
gpon/ neta/ tpm/
kw_cpuidle.0/ orion_wdt/ tpm_sw/
这说明,我的设备的platform总线下,一共挂载了15个设备;
另外,platform总线里边的driver目录是这样的:
/ # ls sys/bus/platform/drivers/
ehci_marvell/ mv64xxx_i2c/ mvsdio/ soc-audio/
kw_cpuidle/ mv88fx_neta/ serial8250/
应该是有7个驱动,可见,platform挂载的设备不一定都有驱动,这是正常的,因为有的设备就是为了调试(即sysfs功能,查看和设置内核参数)设备参数用的,这也就是说明了很多文章讲的“sysfs是linux设备模型的附属物”是什么意思。
再看看/sys/devices目录下的platform目录下有什么内容:
/ # ls sys/devices/platform/
boardEnv/ mv88fx_neta.0/ serial8250/
cust/ mvsdio/ tpm/
ehci_marvell.70059/ neta/ tpm_sw/
gpon/ orion_wdt/ uevent
kw_cpuidle.0/ regulatory.0/
mv64xxx_i2c.0/ serial8250.0/
应该是16个设备,比/sys/bus/platform/devices目录下多了一个regulatory.0/,先不管它,其余都是对应的。
再看看class的情况:
/ # cat sys/class/
bdi/ mem/ pon/ scsi_host/ vc/
firmware/ misc/ ppp/ sound/ vtconsole/
i2c-adapter/ mmc_host/ rtc/ tpm/
i2c-dev/ mtd/ scsi_device/ tty/
ieee80211/ net/ scsi_disk/ ubi/
input/ pci_bus/ scsi_generic/ usb_device/
应该是26个class,有的class下有一个设备,有的class下有很多设备,并且基本不和/sys/devices/platform/或/sys/bus/platform/devices/目录下的内容有什么对应关系,这就更说明了sysfs的灵活性,它专门用于调试的,linux驱动模型中,除了满足设备正常运行的device和driver,还有很多设备是虚拟的,是有意加的,只是为了调试。
下面举几个实际的例子,先粗略的看设备是怎么加入这个模型中去的:
int __init buses_init(void)
{
bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL);
if (!bus_kset)
return -ENOMEM;
return 0;
}
Eg1:函数buses_init告诉linux内核,什么叫做bus,调用函数kset_create_and_add;
int __init platform_bus_init(void)
{
int error;
early_platform_cleanup();
error = device_register(&platform_bus);
if (error)
return error;
error = bus_register(&platform_bus_type);
if (error)
device_unregister(&platform_bus);
return error;
}
Eg2:函数platform_bus_init,首先创建了一个设备叫platform_bus,这是个总线设备(platform是总线,同时本身也是个设备),调用函数device_register;然后创建了一个总线叫platform,调用函数bus_register;
pd = bus_find_device_by_name(&platform_bus_type, NULL, "gpon");
if (!pd) {
platform_device_register_simple("gpon", -1, NULL, 0);
pd = bus_find_device_by_name(&platform_bus_type, NULL, "gpon");
}
Eg3:函数platform_device_register_simple在platform总线下创建了设备gpon,实际调用函数是platform_device_add;
pon_udev_class = class_create(THIS_MODULE, “pon”);
Eg4:函数class_create创建了一个类叫pon;
pon_udev_dev = device_create(pon_udev_class, NULL, dev, NULL, PON_DEV_NAME);
Eg5:紧跟着,函数device_create在类pon下创建了个设备也叫pon;
static struct platform_driver mv64xxx_i2c_driver = {
.probe = mv64xxx_i2c_probe,
.remove = __devexit_p(mv64xxx_i2c_remove),
.driver = {
.owner = THIS_MODULE,
.name = MV64XXX_I2C_CTLR_NAME,
},
};
static int __init
mv64xxx_i2c_init(void)
{
return platform_driver_register(&mv64xxx_i2c_driver);
}
Eg6:函数platform_driver_register在platform总线下创建了驱动mv64xxx_i2c_driver;
retval = bus_register(&i2c_bus_type);
Eg7:函数bus_register创建了又一个总线i2c_bus;
retval = i2c_add_driver(&dummy_driver);
Eg8:函数i2c_add_driver在总线i2c_bus下创建了一个驱动dummy_driver,实际调用函数driver_register;
res = i2c_add_driver(&i2cdev_driver);
Eg9:函数i2c_add_driver在总线i2c_bus下又创建了一个驱动i2cdev_driver,实际调用函数driver_register;
i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");
Eg10:函数class_create创建了一个class,叫i2c-dev;
i2c_adapter_compat_class = class_compat_register("i2c-adapter");
Eg11:函数class_compat_register创建了一个class叫i2c-adapter;
经过上面的11个例子,可以看看,是不是和前面打印的bus、devices、driver、class里的相应内容对应上。
这时,应该可以有了linux设备模型的一个初步的概念,即:
1、 bus、devices、driver、class的每一个内容,都可以对应上sysfs即/sys目录下的一个个目录;
2、 每一个bus、devices、driver、class本身也都是一个设备(这个从代码实现上很好理解,因为它们和具体设备、驱动一样,也必须是一个个目录,只不过是相对的顶层目录罢了,可以从数据结构的树的节点去理解)
3、 bus和class都是在顶层,它们下边是一个个实际的bus、class条目,每个bus条目下有device和driver两个目录,每个目录下可能有一些设备目录,每个class条目下可能有一些设备目录;
只有这些远远不够,后面就要详细描述了,描述这种设备模型到底从代码上是怎么实现的,这就更加深刻的让我们理解linux设备模型到底是什么、sysfs是什么以及怎么用。