Linux中有三种设备类型、块设备、字符设备、网络设备
char device — 字符设备直接通过设备节点(device node,/dev下面)来访问,在应用程序看来,操作某个字符设备节点就和操作某个普通文件是一样的,只不过字符设备节点可能不支持读写指针的移动,它只能按部就班的读写。
block device — 块设备上面能承载文件系统,块设备借助文件系统来访问
net device — 网络设备没有设备节点,它需要借助socket API来访问,所以应用程序不能直接读写网络设备。
除此以外,还能经常看到USB设备、PCI设备、I2C设备等,虽然有这样的称呼,但是它们还是能够归类到之前的三种设备中去,比如USB鼠标就是字符设备、U盘就是块设备、USB网卡就是网络设备。
• 模块
模块(module)的作用是增大内核的功能,因为内核的作用通常来说有进程调度、内存管理、文件系统、网络协议、设备控制等,在内核载入了一个module之后,比方说这个module是某个设备的驱动,那么内核的“设备控制”这一项就变得更强大了。
模块的目的是帮助内核实现某些功能,而模块的初始化就是要告诉内核该模块支持哪里功能。尽管模块可以使内核支持更多的功能,但是要使用这些功能,还是应用程序的事情。应用程序与内核(模块)程序的主要区别是地址空间不同,前者是用户空间,后者是内核空间。从应用空间到内核空间的办法是通过系统调用或者中断,那也就是说模块里面的代码要么是为了系统调用工作,要么就是为了中断处理工作。
Linux内核支持module,这意味着Linux的内核镜像可以动态的插入或删除代码,也就是内核地址空间可以动态改变。每个模块在/sys/modules下可以看到。
一个模块的Makefile为:
obj-$(CONFIG_FISHING_POLE) += fishing.o
fishing-objs := fishing-main.o
fishing-line.o
这个模块的上一层的Makefile也需要做相应的修改
若是module不处于kernel source tree中,则Makefile为:
obj-m := fishing.o
fishing-objs := fishing-main.o
fishing-line.o
编译方法为:
make -C /kernel/source/location
SUBDIRS=$PWD modules
(LDD中比较详细)
查看module的依赖性:depmod
加载module:insmod module.ko
加载module和其依赖:modprobe module [ module parameters ]
一个模块可以依赖于另一个模块的某些函数与变量,这样就需要将被依赖的函数或变量导出到一个叫内核符号表(kernel symbol table)的地方,这个表是全局的,其他模块可以看得到。当一个模块加载进内核后,该模块中所有标记为EXPORT_SYMBOL的函数或变量,都会加入到内核符号表中去。
int get_pirate_beard_color(struct pirate
*p) {
return
p->beard.color;
}
EXPORT_SYMBOL(get_pirate_beard_color);
模块的初始化与结束
在模块初始化的时候,它就要告诉内核它支持某些功能。告诉方法通常是调用一些以“register_”开头的函数,这也是模块初始化函数经常要做的工作。另外,正是因为模块的所要实现功能通常是系统调用的一部分,模块编程是在一个软件的抽象层,它向上需要符合系统调用的接口,所以在模块初始化期间需要分配某些数据结构,如file_operations等等。
- struct something *item1;
- struct somethingelse *item2;
- int stuff_ok;
- void my_cleanup(void)//原本有__exit,但是该函数被其他函数调用,所以不能加
- {
- if (item1)
- release_thing(item1);
- if (item2)
- release_thing2(item2);
- if (stuff_ok)
- unregister_stuff();
- return;
- }
- int __init my_init(void)//注意__init的修饰,目的是模块加载后释放内存
- {
- int err = -ENOMEM;
- item1 = allocate_thing(arguments);
- item2 = allocate_thing2(arguments2);//分配
- if (!item2 || !item2)
- goto fail;
- err = register_stuff(item1, item2);//注册,注意竞争条件
- if (!err)
- stuff_ok = 1;
- else
- goto fail;
- return 0; /* success */
-
- fail:
- my_cleanup();//clean也是按顺序的
- return err;//可返回-ENODEV、-ENOMEM等等
- }
为了在内核configuration中配置自己的module,这个设计到kbuild系统和kconfig文件,上述的CONFIG_FISHING_POLE就在内核configuration的时候被赋值。一个典型的module配置如下:
config FISHING_POLE
tristate “Fish Master 3000 support”
default n
help
If you say Y here, support for the Fish
Master 3000 with computer
interface will be compiled into the kernel
and accessible via a
device node. You can also say M here and
the driver will be built as a
module named fishing.ko.
If unsure, say N.
tristate指三种编译方式(不编译、编译为module、编译至内核)
此外,配置文件中还常见bool、select、depends、if等语法。
• 设备模型
设备模型的建立为的是让内核更好的管理设备,比如组织device、driver、bus之间的关系,电源管理(如掉电顺序)等等。设备模型使用了三个数据结构来描述,kobjects、ktypes、ksets:
Kobjects—描述基本的对象,嵌入在三种设备类型中
Ktypes—描述对象的(一组共享的)方法和属性
Ksets—描述对象的合集
设备模型体现在sysfs文件系统中,其中目录是Kobjects,Ksets是目录或者子目录,目录下的文件是Ktypes。Sysfs的目录如下:
其中,class是高层的设备分类,dev是设备节点,devices是设备拓扑结构。
Kobjects和sysfs都用相应的操作函数,这里就不列了。其中有一个是用于内核来通知用户进程的,函数名kobject_uevent,它用到了Netlink socket?。
• 总线、设备、驱动
总线
- 总线的数据结构:
- struct bus_type {
- char *name;
- struct subsystem subsys;
- struct kset drivers;//总线下的驱动
- struct kset devices;//总线下的设备
- int (*match)(struct device *dev, struct device_driver *drv); //总线设备与驱动的匹配
- struct device *(*add)(struct device * parent, char * bus_id);
- int (*hotplug) (struct device *dev, char **envp, //总线热插拔相关
- int num_envp, char *buffer, int buffer_size);
- /* Some fields omitted */
- };
- 定义并注册一个总线:
- struct bus_type ldd_bus_type = {
- .name = "ldd",
- .match = ldd_match,
- .hotplug = ldd_hotplug,
- };
- ret = bus_register(&ldd_bus_type);
- 遍历总线上的设备与驱动:
- int bus_for_each_dev(struct bus_type *bus, struct device *start,
- void *data, int (*fn)(struct device *, void *));
- int bus_for_each_drv(struct bus_type *bus, struct device_driver *start,
- void *data, int (*fn)(struct device_driver *, void *));
设备- 设备数据结构:
- struct device {
- struct device *parent;
- struct kobject kobj;
- char bus_id[BUS_ID_SIZE];
- struct bus_type *bus;
- struct device_driver *driver;
- void *driver_data;
- void (*release)(struct device *dev);
- /* Several fields omitted */
- };
- 在注册一个设备以前,parent, bus_id, bus, release等需要被初始化。注册设备:
- struct device ldd_bus = {
- .bus_id = "ldd0",
- .release = ldd_bus_release
- };
- ret = device_register(&ldd_bus);
- 更普遍的是先定义总线设备结构,它是对上述device结构的封装:
- struct ldd_device {
- char *name;
- struct ldd_driver *driver;
- struct device dev;
- };
- int register_ldd_device(struct ldd_device *ldddev)
- {
- ldddev->dev.bus = &ldd_bus_type;
- ldddev->dev.parent = &ldd_bus;
- ldddev->dev.release = ldd_dev_release;
- strncpy(ldddev->dev.bus_id, ldddev->name, BUS_ID_SIZE);
- return device_register(&ldddev->dev);
- }
- EXPORT_SYMBOL(register_ldd_device);
- 再定义总线上的某种设备“sculld_dev”,它是对总线设备的封装
- static void sculld_register_dev(struct sculld_dev *dev, int index)
- {
- sprintf(dev->devname, "sculld%d", index);
- dev->ldev.name = dev->devname;
- dev->ldev.driver = &sculld_driver;
- dev->ldev.dev.driver_data = dev;
- register_ldd_device(&dev->ldev);
- device_create_file(&dev->ldev.dev, &dev_attr_dev);
- }
驱动- 驱动结构:
- struct device_driver {
- char *name;
- struct bus_type *bus;
- struct kobject kobj;
- struct list_head devices;
- int (*probe)(struct device *dev);
- int (*remove)(struct device *dev);
- void (*shutdown) (struct device *dev);
- };
- 驱动注册:
- int register_ldd_driver(struct ldd_driver *driver)
- {
- int ret;
- driver->driver.bus = &ldd_bus_type;
- ret = driver_register(&driver->driver);
- if (ret)
- return ret;
- driver->version_attr.attr.name = "version";
- driver->version_attr.attr.owner = driver->module;
- driver->version_attr.attr.mode = S_IRUGO;
- driver->version_attr.show = show_version;
- driver->version_attr.store = NULL;
- return driver_create_file(&driver->driver, &driver->version_attr);
- }
- static struct ldd_driver sculld_driver = {
- .version = "$Revision: 1.1 $",
- .module = THIS_MODULE,
- .driver = {
- .name = "sculld",
- },
- };
总线、设备、驱动是linux中的“设备驱动模型”,linux中最基本的三种设备还是字符设备、块设备、网络设备,而设备驱动模型只是模型,它的目的是为了更好的管理设备。
总线、设备、驱动,其实应该更好的称为:总线、总线设备、总线设备驱动。在总线的驱动程序中(总线也是一个模块,它有自己的驱动),初始化之后遍历总线上的总线设备(比如读某些地址,看是否有有效数据),找到总线设备之后注册总线设备。总线设备驱动(显然是一个模块)被加载后注册总线设备驱动,总线驱动程序发现有总线设备驱动注册或者有总线设备注册的时候,调用“match”方法以匹配总线设备和总线设备驱动,若成功match,在调用总线设备驱动中的“probe”方法,在probe方法中,注册真正的设备类型(如字符、块、网络)。
- 特别说下platform总线,一般板级文件中定义platform总线设备并注册:
- static struct platform_device *smdk2410_devices[] __initdata = {
- &s3c_device_usb,
- &s3c_device_lcd,
- &s3c_device_bl,
- &s3c_device_wdt,
- &s3c_device_i2c,
- &s3c_device_iis,
- &s3c_device_sdi,
- &s3c_device_adc,
- &s3c_device_nand,
- &s3c_device_usbgadget,
- &s3c_device_ts,
- &s3c_device_buttons,
- &s3c_device_rtc,
- &s3c_device_spi0,
- &s3c_device_timer1,//add by cefanty for battery charging
- };
- 设备有资源:
- static struct resource s3c_sdi_resource[] = {
- [0] = {
- .start = S3C2410_PA_SDI,
- .end = S3C2410_PA_SDI + S3C24XX_SZ_SDI - 1,
- .flags = IORESOURCE_MEM,
- },
- [1] = {
- .start = IRQ_SDI,
- .end = IRQ_SDI,
- .flags = IORESOURCE_IRQ,
- },
- [2] = {
- .start = 3,
- .end = 3,
- .flags = IORESOURCE_DMA,
- }
- };
- struct platform_device s3c_device_sdi = {
- .name = "s3c2410-sdi",
- .id = -1,
- .num_resources = ARRAY_SIZE(s3c_sdi_resource),
- .resource = s3c_sdi_resource,
- };
- 驱动模块定义platform总线驱动,并注册驱动
- static struct platform_driver s3c2410sdi_driver =
- {
- .probe = s3c2410sdi_probe,
- .remove = s3c2410sdi_remove,
- .suspend= s3c2410mci_suspend,
- .resume= s3c2410mci_resume,
- .driver={
- .name= "s3c2410-sdi",
- .bus = &platform_bus_type,
- .owner= THIS_MODULE,
- },
- };
- 内核在总线驱动注册或者设备注册的使用会尝试调用match(),如果成功继续调用probe()。这里match()靠的就是看name是否为"s3c2410-sdi"。
• 参考
详见LDD第14章
http://blog.chinaunix.net/uid-526461-id-186728.html
阅读(1921) | 评论(0) | 转发(0) |