Linux 内核设计与实现之第17章 设备与模块

1 设备类型
   Linux中设备被分为三大类:块设备,字符设备,网络设备
   块设备:寻址以块为单位,支持重定位操作(数据的随机访问)。如硬盘,蓝光光碟等。块设备通过"块设备节点"的特殊文件来访问,并通常被挂载为文件系统。
   字符设备:不可寻址,仅通过数据的流式访问。如键盘,鼠标,打印机,大部分伪设备等。字符设备通过"字符设备节点"的特殊文件来访问,应用程序通过直接访问设备节点与字符设备交互。
   网络设备:以太网设备,提供对网络的访问。通过套接字API这样的特殊接口来访问
   
2 模块
  Linux内核是模块化组成的,允许内核在运行时动态的向其中插入或者从中删除代码。
  这些代码(包括相关的子例程,数据,函数入口,函数出口)就是可装载内核模块,简称模块。
  支持模块的好处就是基本内核镜像可以尽可能地小,因为可选的功能和驱动程序可以利用模块形式再提供。模块允许方便的删除和重新载入内核代码。热拔插时可以通过命令载入新的驱动程序
  1)内核模块的编写格式:
  头文件:
  #include <linux/module.h>     //这个头文件包含了许多符号与函数的定义,这些符号与函数多与加载模块有关
  #include <linux/init.h>       //这个头文件包含了你的模块初始化与清除的函数
  #include <linux/kernel.h>           //包含了常用的内核函数
  
  设备或者文件的操作函数
  static struct file_operations nvme_irq_fops = {
       owner   : THIS_MODULE,
       open    : nvme_irq_open,
       read    : nvme_irq_read,
       write   : nvme_irq_write,
       release : nvme_irq_release,
  };
  
  初始化函数和终了函数
  static int init_module(void)
  {
        注册资源,初始化硬件,分配数据结构等
          . ..
       return 0;
  }
  static void fini_module(void)  //出口函数不能有返回值
  {
        清理资源
        ...
  }
  
  初始化函数和终了函数的入口函数,通过例程module_init()向内核注册到系统中
  module_init(init_module);
  module_exit(fini_module);
  
  描述性声明:
  MODULE_LICENSE("GPL");        // 指定模块的版权。"GPL" 是指明了这是GNU General Public License的任意版本
// “GPL v2” 是指明 这仅声明为GPL的第二版本
  MODULE_AUTHOR                 // 声明作者
  MODULE_DESCRIPTION            // 对这个模块作一个简单的描述,这个描述是"human-readable"的
  MODULE_VERSION                // 这个模块的版本
  MODULE_ALIAS                  // 这个模块的别名
  MODULE_DEVICE_TABLE           // 告诉用户空间这个模块支持什么样的设备
  
  2)步骤
  1 编写一个内核模块文件,如module.c,规则如上
  2 编写Makefile文件。 编写如下:
      obj-m := xxx.o
      KERNELDIR := /lib/modules/$(shell uname -r)/build
      PWD := $(shell pwd)
      default:
                $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
      clean:
                rm -f *.o *.ko *.mod.c *.order *.symvers
  3 加载此模块(.ko)到运行的内核中
       insmod xxx.ko 或者 modprobe xxx.ko
     从内核中卸载此模块
       rmmod xxx.ko  或者 modprobe -r xxx.ko
3)构建模块:
  1 将模块源码放到内核源码树中
设备驱动程序放在/drivers的子目录下,字符设备放在/drivers/char/下,块设备放在/drivers/block/目录下,USB设备放在/drivers/usb/目录下等等
如果只有一两个源文件,可以直接放在该目录下,如果源文件很多,需要将其放在一个文件夹(如module)中,再放在该目录(如/drivers/char/)下,接下来需要在该目录(如/drivers/char/)的Makefile文件中加入
obj-m += module/
告诉系统,在编译模块时需要进入module/子目录中。
如果模块的编译取决于一个特殊的配置选项,如CONFIG_NVMERR,那么将上述的指令修改为:
obj-$(CONFIG_NVMERR) += module/
最后在nvme_err/子目录中中添加一个新的Makefile文件:
obj-m += module.o(如果源文件名为module.c,虽然写的扩展名是.o,但是实际上模块编译后的扩展名是.ko)
  2 将模块放到内核代码外
建立新的Makefile:
obj-m := module.o
如果多个源文件:
obj-m := module.o
module-objs := module-main.o module-sub.o(假如源文件module-main.c module-sub.c)
  区别在于:在内核源码树外的模块需要make时需要找到内核源码文件和Makefile基础文件:
  obj-m := module.o
  KERNELDIR := /lib/modules/$(shell uname -r)/build
  PWD := $(shell pwd)
  default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
  clean:
rm -f *.o *.ko *.mod.c *.order *.symvers
  
  4)管理配置选项
      在静态编译模块(即将模块编译进内核)时,需要修改Kconfig文件来增加对新增模块的配置菜单。
  Kconfig文件:分布在各目录下的Kconfig构成了一个分布式的内核配置数据库,每个Kconfig分别描述了所属目录源文件相关的内核配置菜单。在内核配置make menuconfig(或xconfig等)时,从Kconfig中读出配置菜单,用户配置完后保存到.config(在顶层目录下生成)中。在内核编译时,主Makefile调用这个.config,就知道了用户对内核的配置情况。
  Kconfig文件的例子:
  config XXX_XXX  
        tristate/bool "Tmpfs POSIX Access Control Lists"  
depends on ARM || BLACKFIN || MIPS
select GENERIC_ACL
default y
help
POSIX Access Control Lists (ACLs) support permissions for users and
groups beyond the owner/group/world scheme.
  解释:
  第一行,config是关键字,表示一个配置选项的开始。XXX_XXX是配置选项的名字,省略了前缀CONFIG_,这个选项会在.config生成一个CONFIG_XXX_XXX变量并且给其赋相应的值,用来在makefile中使用。 
  第二行,声明选项类型。
          如果编译选项代表一个模块。tristate:意思是三态(3种状态,对应Y(可以编译进内核)、N(不编译)、M(模块编译)三种选择方式),意思就是这个配置项可以被三种选择。
  如果编译选项代表一个系统功能。bool
  第三行,depends on指令本配置项依赖于另一个配置项。依赖的配置项为Y或者M时,则本配置项才有意义。
  第四行,select指令会强行打开被指定的选项
  第五行,指定该选项的默认选择。默认是不编译(N)。可以指定为Y或者M
  第六行,为该选项提供帮助文档。各种配置工具均可以显示这些帮助。
  5)模块参数
     首先定义一个全局变量:
   static int param;
然后通过宏module_param()定义模块参数:
       module_param(param, type, perm)
   name:模块参数的变量名
   type:参数类型
   perm:指定模块在sysfs文件系统下对应文件的权限
   
  6)导出符号表
     EXPORT_SYMBOL()或者EXPORT_SYMBOL_GPL()
通过上述导出符号表导出的内核函数可以被其他模块调用(或者仅被标记为GPL协议的模块调用)。
 
3 统一设备模型
  Linux是通过设备与设备类来管理设备的,当调用函数向系统注册设备及其类的时候,内核会自动的在sysfs文件系统中创建对应的文件。
  Linux的统一设备模型,就是在设备管理功能中,将重复的、具有共性的部分抽象出来构成固定的框架,同时将变化的、具有具体设备特性的部分分离出来,并作为参数传递给固定框架,最终完成整体的设备管理功能。
  1)sysfs
     Sysfs文件系统是一个类似于proc文件系统的特殊文件系统,用于将系统中的设备组织成层次结构,并向用户模式程序提供详细的内核数据结构信息。帮助用户以一个简单文件系统的方式来观察系统中各种设备的拓扑结构。
Linux的/sys目录结构: 
block/        包含所有的块设备
bus/          包含系统中所有的总线类型   
class/        系统中的设备类型(如网卡设备,声卡设备等)
devices/      包含系统所有的设备,并根据设备挂接的总线类型组织成层次结构
firmware/     包含底层子系统的特殊树
kernel/       包含内核配置项和状态信息
module/       包含系统已加载模块的信息
power/        包含系统范围的电源管理数据
sysfs文件系统的信息来源是kobject层次结构。kobject层次结构就是linux的设备模型
 
  2)kobject
     kobject可认为是所有设备的基类。定义在头文件<linux/kobject.h>
struct kobject {
       const char                  *name;       /*kobject的名字,每个kobject都对应着sysfs下的一个文件夹,该名字也是对应的文件夹的名字。*/
       struct list_head             entry;      /*双向链表指针,用于将同一kset集合中的kobject链接到一起,便于访问*/
       struct kobject              *parent;     /*kobject对应的父kobject节点,在sysfs表现为上一级目录*/
       struct kset                 *kset;       /*kobject所在的集合的指针*/
       struct kobj_type            *ktype;      /*kobject对象类型指针*/
       struct sysfs_dirent         *sd;         /*sd用于表示VFS文件系统的目录项,是设备与文件之间的桥梁。在sysfs节会对此结构进行分析*/
       struct kref                  kref;        /*对象引用计数器*/
       unsigned int state_initialized:1;         /*初始化标志位*/
       unsigned int state_in_sysfs:1;            /*kobject对象在sysfs中的状态,即kobject对应的目录在sysfs中是否被创建*/
       unsigned int state_add_uevent_sent:1;     /*添加设备时会向用户空间发送uevent事件,请求新增设备*/
       unsigned int state_remove_uevent_sent:1;  /*删除设备时会向用户空间发送uevent事件,请求卸载设备*/
       unsigned int uevent_suppress:1;
     };
注:
■ ktype
       ktype是为了描述kobject的普遍特性。
   struct kobj_type {
  void (*release)(struct kobject *);     //release方法用于释放kobject占用的资源
  struct sysfs_ops * sysfs_ops;          //sysfs ops指针指向sysfs操作表和一个sysfs文件系统缺省属性列表
  struct attribute ** default_attrs;     //定义该kobject相关的默认属性(析构行为,sysfs行为等,这些属性负责将内核数据映射成sysfs中的文件)
   };
   sysfs文件系统中,最重要的就是struct attribute结构,它被用来管理内核sysfs文件的接口(名字,属性,读写函数等)。
   struct attribute {  
  const char        *name;           //属性名称。最终出现在sysfs中的文件名也是它
  struct module     *owner;          //所属模块
  mode_t             mode;           //sysfs中该文件的权限
   };
   struct sysfs_ops结构则是描述如何使用这些属性。
   struct sysfs_ops {
      //在读sysfs文件时该方法被调用,将attr中的属性拷贝到buffer中
  ssize_t (*show)(struct kobject *kobj,
   struct attribute *attr, char *buf);
  //在写sysfs文件时该方法被调用,从buffer中读取count大小的字节,并将其放到attr表示的属性结构体变量中
  ssize_t (*store)(sturct kobject *kobj, 
   strcut atrribute *attr, const char *buf,
   size_t count);
   };
   一般来说,ktype中提供的默认属性是充足的。但是内核也提供了添加或删除属性的函数:
   /* 为kobj创建或删除属性 */ 
int sysfs_create_file(struct kobject *kobj, const struct attribute *attr);
void sysfs_remove_file(struct kobject *kobj, const struct attribute *attr);
■ kset
   Kobject通过kset组织成层次化的结构,kset是具有相同类型的kobject的集合。
   kobject 会根据kset分辨自已是属于那一個类型,然後在/sys 下建立正确的目录位置。比如全部的块设备置于同一位置。
   struct kset {
struct subsystem * subsys;   //所在的subsystem的指针
struct kobj_type * ktype;    //指向该kset对象类型描述符的指针
struct list_head list;       //用于连接该集合中所有kobject的对象
struct kobject kobj;         //嵌入的kobject
struct kset_hotplug_ops * hotplug_ops; //指向热插拔操作表的指针
   };
对象引用计数器的作用:kobject在引用计数为0时才能动态的释放内存资源。只要引用计数不为0,该对象就会继续保留在内存中。这样kobject的上层概念如device也应该能够动态的释放内存资源
parent指针的作用:
相关函数:将所有包含kobject的设备构造成树形结构,便于统一管理
     kobject_init();// kobject 初始化函数;
     kobject_add();  //把kobject导入sysfs。挂接该kobject对象到kset的list链中,增加父目录各级kobject的引用计数,在其parent指向的目录下创建文件节点,并启动该类型内核对象的hotplug函数
     kobject_init_and_add(); //kobject_init() and kobject_add()函数的结合,返回值与kobject_add()相同;与kobject_create_and_add的区别是,kobject结构体必须已经创建好,动态创建或者静态声明均可;
     kobject_del();  //从sysfs中删除一个kobject对应文件目录;
     kobject_create();  //动态的创建一个kobject结构体;
     kobject_create_and_add(); // kobject_create_and_add动态创建了一个kobject结构体,将其初始化,将其加入到kobject层次中,并最终返回所创建的 kobject的指针,当然如果函数执行失败,则返回NULL;
     kobject_rename(); //改变一个kobject的名字;
     kobject_move();   //将一个kobject从一个层次移动到另一个层次;
     kobject_get();    //将kobj 对象的引用计数加1,同时返回该对象的指针;
     kobject_put();    //将kobj 对象的引用计数减1,如果引用计数降为0,则调用kobject_release()释放该kobject 对象;
     kobject_get_path(); //返回kobject的路径;
     kobject_set_name(); //设置kobject的名字
kobject_register();  //kobject注册函数。
kobject_unregister();  //kobject注销函数
 
4 内核事件层Uevent
  当Hotplug事件发生时,系统会自动检测Hotplug事件,并且完成设备的枚举或删除过程。但是,如果内核中不存在对应设备的驱动时,设备驱动可能以模块的形式保存在硬盘上,此时载入/卸载驱动必然需要从用户态进行。这里就需要Linux的uevent机制,当设备加入/删除时,将设备信息发送消息给用户态。
  Uevent是Kobject的一部分,用于在Kobject状态发生改变时,例如增加、移除等,通知用户空间程序。用户空间程序收到这样的事件后,会做相应的处理。
  kobject对应的动作:
  enum kobject_action {
     KOBJ_ADD,
     KOBJ_REMOVE,
     KOBJ_CHANGE,
     KOBJ_MOVE,
     KOBJ_ONLINE,
     KOBJ_OFFLINE,
     KOBJ_MAX
  };
  对应的事件:
  static const char *kobject_actions[] = {
     [KOBJ_ADD] =        "add",
     [KOBJ_REMOVE] =     "remove",
     [KOBJ_CHANGE] =     "change",
     [KOBJ_MOVE] =       "move",
     [KOBJ_ONLINE] =     "online",
     [KOBJ_OFFLINE] =    "offline",
  };
  uevent事件是以环境变量的形式发送到用户空间:
  struct kobj_uevent_env {
     char *envp[UEVENT_NUM_ENVP]; /*环境变量指针数组*/
     int envp_idx; /*数组下标*/
     char buf[UEVENT_BUFFER_SIZE];
     int buflen;
  };
  发送函数:
  int kobject_uevent(struct kobject *kobj, enum kobject_action action);

  可以通过两个途径把事件上报到用户空间:
  1 通过kmod模块,直接调用用户空间的可执行文件;
  2 通过netlink通信机制,将事件从内核空间传递给用户空间。
  
  
  
http://blog.csdn.net/u012066426/article/details/51917369 
https://www.cnblogs.com/sky-heaven/p/6394267.html
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值