Chinaunix首页 | 论坛 | 博客
  • 博客访问: 83054
  • 博文数量: 11
  • 博客积分: 386
  • 博客等级: 一等列兵
  • 技术积分: 240
  • 用 户 组: 普通用户
  • 注册时间: 2011-12-02 17:11
文章分类

全部博文(11)

文章存档

2012年(11)

我的朋友

分类: LINUX

2012-09-16 13:12:58

     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

加载moduleinsmod 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等等。


  1. struct something *item1;
  2. struct somethingelse *item2;
  3. int stuff_ok;

  4. void my_cleanup(void)//原本有__exit,但是该函数被其他函数调用,所以不能加
  5. {
  6.     if (item1)
  7.         release_thing(item1);
  8.     if (item2)
  9.         release_thing2(item2);
  10.     if (stuff_ok)
  11.         unregister_stuff();
  12.     return;
  13. }

  14. int __init my_init(void)//注意__init的修饰,目的是模块加载后释放内存
  15. {
  16.     int err = -ENOMEM;
  17.     item1 = allocate_thing(arguments);
  18.     item2 = allocate_thing2(arguments2);//分配
  19.     if (!item2 || !item2)
  20.         goto fail;

  21.     err = register_stuff(item1, item2);//注册,注意竞争条件
  22.     if (!err)
  23.         stuff_ok = 1;
  24.     else
  25.         goto fail;

  26.     return 0; /* success */
  27.     
  28. fail:
  29.     my_cleanup();//clean也是按顺序的
  30.     return err;//可返回-ENODEV、-ENOMEM等等
  31. }
    为了在内核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、编译至内核)

此外,配置文件中还常见boolselectdependsif等语法。

 

设备模型

设备模型的建立为的是让内核更好的管理设备,比如组织devicedriverbus之间的关系,电源管理(如掉电顺序)等等。设备模型使用了三个数据结构来描述,kobjectsktypesksets

Kobjects—描述基本的对象,嵌入在三种设备类型中

Ktypes—描述对象的(一组共享的)方法和属性

Ksets—描述对象的合集

设备模型体现在sysfs文件系统中,其中目录是KobjectsKsets是目录或者子目录,目录下的文件是KtypesSysfs的目录如下:

其中,class是高层的设备分类,dev是设备节点,devices是设备拓扑结构。

Kobjectssysfs都用相应的操作函数,这里就不列了。其中有一个是用于内核来通知用户进程的,函数名kobject_uevent,它用到了Netlink socket?。

 

总线、设备、驱动

总线

  1. 总线的数据结构:
  2. struct bus_type {
  3.     char *name;
  4.     struct subsystem subsys;
  5.     struct kset drivers;//总线下的驱动
  6.     struct kset devices;//总线下的设备
  7.     int (*match)(struct device *dev, struct device_driver *drv); //总线设备与驱动的匹配
  8.     struct device *(*add)(struct device * parent, char * bus_id);
  9.     int (*hotplug) (struct device *dev, char **envp, //总线热插拔相关
  10.                     int num_envp, char *buffer, int buffer_size);
  11.     /* Some fields omitted */
  12. };

  13. 定义并注册一个总线:
  14. struct bus_type ldd_bus_type = {
  15.     .name = "ldd",
  16.     .match = ldd_match,
  17.     .hotplug = ldd_hotplug,

  18. };
  19. ret = bus_register(&ldd_bus_type);

  20. 遍历总线上的设备与驱动:
  21. int bus_for_each_dev(struct bus_type *bus, struct device *start,
  22.                      void *data, int (*fn)(struct device *, void *));

  23. int bus_for_each_drv(struct bus_type *bus, struct device_driver *start,
  24.                      void *data, int (*fn)(struct device_driver *, void *));
设备
  1. 设备数据结构:
  2. struct device {
  3.     struct device *parent;
  4.     struct kobject kobj;
  5.     char bus_id[BUS_ID_SIZE];
  6.     struct bus_type *bus;
  7.     struct device_driver *driver;
  8.     void *driver_data;
  9.     void (*release)(struct device *dev);
  10.     /* Several fields omitted */
  11. };

  12. 在注册一个设备以前,parent, bus_id, bus, release等需要被初始化。注册设备:
  13. struct device ldd_bus = {
  14.     .bus_id = "ldd0",
  15.     .release = ldd_bus_release
  16. };
  17. ret = device_register(&ldd_bus);

  18. 更普遍的是先定义总线设备结构,它是对上述device结构的封装:
  19. struct ldd_device {
  20.     char *name;
  21.     struct ldd_driver *driver;
  22.     struct device dev;
  23. };

  24. int register_ldd_device(struct ldd_device *ldddev)
  25. {
  26.     ldddev->dev.bus = &ldd_bus_type;
  27.     ldddev->dev.parent = &ldd_bus;
  28.     ldddev->dev.release = ldd_dev_release;
  29.     strncpy(ldddev->dev.bus_id, ldddev->name, BUS_ID_SIZE);
  30.     return device_register(&ldddev->dev);
  31. }
  32. EXPORT_SYMBOL(register_ldd_device);

  33. 再定义总线上的某种设备“sculld_dev”,它是对总线设备的封装
  34. static void sculld_register_dev(struct sculld_dev *dev, int index)
  35. {
  36.     sprintf(dev->devname, "sculld%d", index);
  37.     dev->ldev.name = dev->devname;
  38.     dev->ldev.driver = &sculld_driver;
  39.     dev->ldev.dev.driver_data = dev;
  40.     register_ldd_device(&dev->ldev);
  41.     device_create_file(&dev->ldev.dev, &dev_attr_dev);
  42. }
驱动
  1. 驱动结构:
  2. struct device_driver {
  3.     char *name;
  4.     struct bus_type *bus;
  5.     struct kobject kobj;
  6.     struct list_head devices;
  7.     int (*probe)(struct device *dev);
  8.     int (*remove)(struct device *dev);
  9.     void (*shutdown) (struct device *dev);
  10. };

  11. 驱动注册:
  12. int register_ldd_driver(struct ldd_driver *driver)
  13. {
  14.     int ret;
  15.     driver->driver.bus = &ldd_bus_type;
  16.     ret = driver_register(&driver->driver);
  17.     if (ret)
  18.         return ret;

  19.     driver->version_attr.attr.name = "version";
  20.     driver->version_attr.attr.owner = driver->module;
  21.     driver->version_attr.attr.mode = S_IRUGO;
  22.     driver->version_attr.show = show_version;
  23.     driver->version_attr.store = NULL;

  24.     return driver_create_file(&driver->driver, &driver->version_attr);
  25. }

  26. static struct ldd_driver sculld_driver = {
  27.     .version = "$Revision: 1.1 $",
  28.     .module = THIS_MODULE,
  29.     .driver = {
  30.         .name = "sculld",
  31.     },
  32. };
    总线、设备、驱动是linux中的“设备驱动模型”,linux中最基本的三种设备还是字符设备、块设备、网络设备,而设备驱动模型只是模型,它的目的是为了更好的管理设备。

总线、设备、驱动,其实应该更好的称为:总线、总线设备、总线设备驱动。在总线的驱动程序中(总线也是一个模块,它有自己的驱动),初始化之后遍历总线上的总线设备(比如读某些地址,看是否有有效数据),找到总线设备之后注册总线设备。总线设备驱动(显然是一个模块)被加载后注册总线设备驱动,总线驱动程序发现有总线设备驱动注册或者有总线设备注册的时候,调用“match”方法以匹配总线设备和总线设备驱动,若成功match,在调用总线设备驱动中的“probe”方法,在probe方法中,注册真正的设备类型(如字符、块、网络)。

  1. 特别说下platform总线,一般板级文件中定义platform总线设备并注册:
  2. static struct platform_device *smdk2410_devices[] __initdata = {
  3.     &s3c_device_usb,
  4.     &s3c_device_lcd,
  5.     &s3c_device_bl,
  6.     &s3c_device_wdt,
  7.     &s3c_device_i2c,
  8.     &s3c_device_iis,
  9.     &s3c_device_sdi,
  10.     &s3c_device_adc,
  11.     &s3c_device_nand,
  12.     &s3c_device_usbgadget,
  13.     &s3c_device_ts,
  14.     &s3c_device_buttons,
  15.     &s3c_device_rtc,
  16.     &s3c_device_spi0,
  17.     &s3c_device_timer1,//add by cefanty for battery charging
  18. };

  19. 设备有资源:
  20. static struct resource s3c_sdi_resource[] = {
  21.     [0] = {
  22.         .start = S3C2410_PA_SDI,
  23.         .end = S3C2410_PA_SDI + S3C24XX_SZ_SDI - 1,
  24.         .flags = IORESOURCE_MEM,
  25.     },
  26.     [1] = {
  27.         .start = IRQ_SDI,
  28.         .end = IRQ_SDI,
  29.         .flags = IORESOURCE_IRQ,
  30.     },
  31.     [2] = {
  32.         .start = 3,
  33.         .end = 3,
  34.         .flags = IORESOURCE_DMA,
  35. }
  36. };

  37. struct platform_device s3c_device_sdi = {
  38.     .name = "s3c2410-sdi",
  39.     .id = -1,
  40.     .num_resources = ARRAY_SIZE(s3c_sdi_resource),
  41.     .resource = s3c_sdi_resource,
  42. };

  43. 驱动模块定义platform总线驱动,并注册驱动
  44. static struct platform_driver s3c2410sdi_driver =
  45. {
  46.     .probe = s3c2410sdi_probe,
  47.     .remove = s3c2410sdi_remove,
  48.     .suspend= s3c2410mci_suspend,
  49.     .resume= s3c2410mci_resume,
  50.     .driver={
  51.         .name= "s3c2410-sdi",
  52.         .bus = &platform_bus_type,
  53.         .owner= THIS_MODULE,
  54.     },
  55. };

  56. 内核在总线驱动注册或者设备注册的使用会尝试调用match(),如果成功继续调用probe()。这里match()靠的就是看name是否为"s3c2410-sdi"


参考

详见LDD14

http://blog.chinaunix.net/uid-526461-id-186728.html

阅读(1856) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~