驱动基础笔记

一,内核模块
 模块可以控制内核的大小,一旦被加载,它和内核中其它功能一样.
 用户可以lsmod分析/proc/modules文件.或 cat /proc/modules.
 模块被加载后,会生成/sys/module/hello目录,包括refcnt文件和sections目录.
 modprobe比insmod强大,它在加载时还可以加载模块所依赖的其它模块.modprobe -r filename卸载模块及其依赖.
 modinfo<模块名>可获得模块信息,包括filename,license,author,description,alias,vermagic,depends等
模块加载
 static int __init initialization_function( void )
 {
  //初始化代码     __init的代码在连接时都放在.init.text.其函数指针放在.initcall.init区段.
        初始化时会调用__init函数,完成后就释放text区段(.init.text  .initcall.init).
 }
 module_init( initialization_function );  成功则返回0,失败则-ENODEV,-ENOMEM等,<linux/errno.h>,用户可用perror打印.
 通过request_module(const char *fmt,...);
     request_module("char-major-%d-%d",MAJOR(dev),MINOR(dev));加载模块
模块卸载
 static void __exit cleanup_function( void )
 {
  //释放代码
 }
 module_exit( clean_function );   无返回
 模块加载函数注册了xxx,卸载就要注销xxx.
 模块加载函数申请了内存,卸载就要释放内存.
 模块加载函数申请了硬件资源(中断,DMA通道,IO端口,IO内存等),卸载就要释放这些资源.
 模块加载函数开启了硬件,卸载就要关闭硬件.
 其实__init和__exit是两个宏.
 #define __init __attribute__ ((__section__(".init.text")))
 #define __exit __attribute__ ((__section__(".exit.text")))
 #define __initdata __attribute__ ((__section__(".init.data")))
 #define __exitdata __attribute__ ((__sectione__)(".exit.data"))
模块许可证声明
 MODULE_LICENSE("Dual BSD/GPL");
模块参数(可选)
 module_param( 参数名, 参数类型, 参数读/写权限 ) 
 参数类型: byte,short,ushort,int,uint,long,ulong,charp(字符指针),bool或invbool.编译时将此参数类型和定义的类型比较.
 读/写权限: 一般为0,不为0时,在/sys/module/目录下会出现以参数名命名的文件节点.文件的权限就是"参数读/写权限".内容是参数的值.
 module_param_array( 数组名,数组类型,数组长,参数读/写权限 )
 数组长:  数组长指针的变量赋给"数组长",或NULL
 static char *book_name = "书名";
 static int num = 4000;
 module_param( book_name, charp, S_IRUGO );
 module_param( num, int, S_IRUGO );
 insmod book.ko     不带参数加载,使用模块内的参数默认值,内核输出在日志"/var/log/messages"
 insmod book.ko book_name='Book name' num=500 带参数加载.
模块导出符号(可选)
 linux2.6的所有内核符号(内核符号表)在 /proc/kallsyms 文件中.
 EXPORT_SYMBOL( 符号名 );    导出内核符号
 EXPORT_SYNBOL_GPL( 符号名 );   用于包含GPL许可权的模块
模块声明和描述(可选)
 MODULE_AUTHOR( author );
 MODULE_DESCRIPTION( description );
 MODULE_VERSION( version_string );
 MODULE_DEVICE_TABLE( table_info );
 MODULE_ALIAS( alternate_name );
 static struct usb_device_id skel_table[] = {
  { USB_DEVICE( USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID ) },
  { } //表结束
 };
 MODULE_DEVICE_TABLE( usb, skel_table );
模块的使用计数(可选)
 2.4中,模块自身控制计数,通过宏:MOD_INC_USE_COUNT, MOD_DEC_USE_COUNT
 2.6中,提供了模块管理计数接口. 此模块管理 struct module *owner域,try_module_put(dev->owner).
 int try_module_get( struct module *module );  在驱动底层使用,如总线驱动或此类设备的共用核心模块.
 void module_put( struct module *module );
模块的编译
 hello.c Makefile(obj-m:= hello.c)
 make -C /usr/src/linux2.6.15.5/ M=/driver_study/ modules
 make -C /usr/src/linux2.6.15.5  M=$(pwd) modules
模块与GPL
 1,内核编译时 enable loadable module support
 2,将.ko文件放入相关目录.
 3,文件系统中应包括insmod,lsmod,rmmod等工具,modprobe,rmmod可以不要.
 4,用户可以insmod手动添加模块.
 5,自动添加在驱动过程中的rc脚本
  mount /proc
  mount /var
  mount /dev/pta
  mkdir /var/log
  mkdir /var/run
  mkdir /var/ftp
  mkdir -p /var/spool/cron
  mkdir /var/config
  ...
  insmod /usr/lib/company_driver.ko 2> /dev/null
  /usr/bin/userprocess
  /var/config/rc

二,Linux文件系统与设备文件系统
 linux文件操作的系统调用,C库的文件操作函数.
linux文件系统的目录:
 /bin /boot /dev /etc /home /lib /lost+found /mnt /opt /proc /root /sbin /tmp /usr /var /sys /initrd
 file结构体,在内核中代表一个打开的文件.
 struct file
 {
  union
  { struct list_head fu_list;
   struct rcu_head fu_rcuhead;
  }f_u;
  struct dentry *f_dentry;   与文件关联的目录入口 dentry结构
  struct vfsmount *f_vfsmnt;   
  struct file_operations *f_op;  与文件关联的操作
  atomic_t f_count;
  unsigned int f_flags;   文件标志,O_RDONLT,O_NOBLOCK,O_SYNC
  mode_t f_mode;    文件读写模式,FMODE_READ和FMODE_WRITE
  loff_t f_pos;     当前读写位置
  struct fown_struct f_owner;
  unsigned int f_uid, f_gid;
  struct file_ta_state f_ra;
  unsigned long f_version;
  void *f_security;
  void *private_data;    文件私有数据,tty驱动使用,其它驱动可能使用
  #ifdef CONFIG_EPOLL
   struct list_head f_ep_links; 被fs/eventpoll.c使用,以便连接所有这个文件的钩子(hooks).
   spinlock_t f_ep_lock;
  #endif
  struct address_space *f_mapping;
 };
 inode结构体,包含文件访问权限,属主,组,大小,生成时间,访问时间,修改时间等信息. 是linux管理file的基本单位,也是vfs连接目录和文件的桥梁.
 struct inode
 {
  ...
  umode_t i_mode;    inode 权限
  uid_t i_uid;     inode 拥有者 id
  gid_t i_gid;     inode 所属的群组 id
  dev_t i_rdev;     设备编号,高12位是主设备编号,低20位是次设备编号.
  loff_t i_size;    inode代表的文件大小

  struct timespec i_atime;   inode最近一次的存取时间
  struct timespec i_mtime;   inode最近一次的修改时间
  struct timespec i_ctime;   inode的产生时间

  unsigned long i_blksize;   inode在做IO时的区块大小
  unsigned long i_blocks;   inode使用的block数,一个block为512byte.

  struct block_device *i_bdev;  块设备的block_device结构体指针.
  struct cdev *i_cdev;    字符设备的cdev结构体指针.
  ...
 }
 unsigned int imajor( struct inode *inode )
 unsigned int iminor( struct inode *inode )
 /proc/devices文件包含系统中注册的设备信息,/dev目录中是系统中包含的设备文件.
 内核的Documents目录下的 devices.txt文件描述了linux设备号的官方分配情况,由LANANA组织维护.

sysfs文件系统与linux设备模型
 2.6引入了sysfs文件系统,用于设备的管理.sysfs与proc,devfs和devpty同级.是一个虚拟的文件系统,包括所有系统硬件的层级视图.
 sysfs把系统里的设备和总线组织成一个分级的文件,可以由用户存取.向用户空间导出了内核数据结构和属性.
 还向用户展示了设备驱动模型的各组件的层次关系和交叉关系:
 block所有的块设备,
 device所有的设备并按总线类型组织成层次结构,
 bus系统中所有的总线类型
 drivers内核中已注册的设备驱动程序
 class系统中的设备类型(网卡,声卡,输入设备)
 power,firmware

 内核将利用以下的数据结构完成linux的设备模型与总线和其他子系统的交互:
 kobject,kset,subsystem,device,device_driver,bus_type,class,class_device,class_interface.
   1,kobject内核对象: 使得所有设备在底层拥有统一的接口,提供了管理对象的基本能力.是2.6设备模型的核心结构,每个kobject对应sysfs一个目录.
  struct kobject
  {
   char *k_name;    
   char name[KOBJ_NAME_LEN];  内核对象的名字
   struct kref kref;   对象引用计数,kobject_get(),kobject_put().
   struct list_head entry;  用于挂接kobject到kset链表
   struct kobject *parent;  父对象指针
   struct kest *kest;   所属kset指针
   struct kobj_type *ktype;  对象类型描述符的指针
   struct dentry *dentry;  sysfs文件系统中与该对象对应的文件节点入口
  };
  struct kobject_type
  {
   void (*release)(struct kobject *);release函数
   struct sysfs_ops * sysfs_ops; 属性操作,store(),show()实现用户空间到设备模型的属性读写.buffer
   struct attribute ** default_attrs;sysfs默认属性列表
  }
  struct ssyfs_ops
  {
   ssize_t (*show)(struct kobject *, struct attibute *, char *);
   ssize_t (*store)(strict kobject *, struct attibute *, const char *, size_t);
  }
 void kobject_init( struct kobject *kobj );
  kref+1,entry指向本身,kset计数+1.
 int kobject_set_name( struct kobject *kobj, const char *format, ...);
 void kobject_cleanup( struct kobject *kobj );void kobject_release( struct kref *kref );
  清除kobject,当0时释放资源.
 struct kobject *kobject_get( struct kobject *kobj );
  kref+1,同时返回对象的指针.
 void kobject_put( struct kobject *kobj );
  kref-1,当0时释放资源.
 int kobject_add( struct kobject *kobj );
  将kobject加入linux设备层次,挂接kobject到kset的list链中.增加父目录中各级kobject的引用计数,
  在其parent指向的目录下创建节点,并启动该类型kobject的hotplug函数.
 int kobject_register( struct kobject *kobj );
  先kobject_init(),再kocject_add().
 void kobject_del( struct kobject *kobj );
  从linux设备层次(hierarchy)结构中删除kobject.
 void kobject_unregister( struct kobject *kobj );
  先kobject_del,再kobject_out()减少kref,kref=0则释放kobject.

  2,kset内核对象集合:
 kobject通过kset组织成层次化的结构,kset是据有相同类型的kobject的集合.
  struct kset
  {
   struct subsystem *subsys;  所在subsystem指针
   struct kobj_type *ktype;  kset对象属性的指针
   struct list_head list;  链接kset中所有kobject的双向链表头
   spinlock_t list_lock;
   struct kobject kobj;   嵌入的kobject,作为kobject的parent,还用于kset中kobject个数的计数.
   struct kset_uevent_ops *uevent_ops;事件操作集
  }
 kset_int(); kset_get(); kest_put(); kset_add(); kset_del(); kset_register();  kset_unregister()
 kobject创建和删除时会产生事件,kset会过滤事件和设置一些事件(热插拔时uevnet_ops执行).这些事件变量被导出到用户空间.
 struct kset_uevent_ops
 {
  int (*filter)(struct kset *kset, struct kobject *kobj);  kobject事件过滤
  const char *(*name)(struct kest *kset, struct kobject *kobject); 
  int (*uevent)(struct kset *kset, struct kobject *kobj, char **envp, 新环境变量设置,导出给用户热插拔处理程序
    int num_envp, char *buffer, int buffer_size);
 }
  导出的环境变量:
  PCI设备:
   ACTION(add/remove),PCA_CLASS(hex的PCI类,子类,接口,c0310),PCI_ID(Vendor:Device,0123:4567),
   PCI_SUBSYS_ID(Sub Vendor:Sub Device,89ab:cdef),PCI_SLOT_NAME(Bus:Slot.Func, 00:07.2).
  USB设备:
   ACTION(add/remove),DEVPATH(/sys/DEVPATH),PRODUCT(idVendor/idProduct/bcdDevice,46d/c281/108),
   TYPE(bDeviceClass/bDeviceSubClass/BdeviceProtocal,9/0/0),INTERFACE(bInterfaceClass/bIntergaceSubClass
   /bInterfaceProtocal,3/1/1),DEVFS(/proc/bus/usb),DEVICE(USB设备节点路径). 
  网络设备:
   ACTION(register/unregister),INTERFACE(eth0)
  输入设备:
   ACTION,PRODUCT,NAME,PHYS,EV,KEY,LED
  IEEE1394设备:
   ACTION,VENDOR_ID,GUID,SPECIFIER_ID,VERSION

 用户程序的热插拔脚本根据传入的参数和导出的环境变量采取行动.
 if [ "$1" = "usb" ]; then      USB热插拔脚本
  if [ "$PRODUCT" = "82d/100/0"]; then
   if[ "$ACTION" = "add" ]; then
    /sbin/modprobe visor
   else
    /sbin/rmmod visor
   fi
  fi
 fi

  3,subsytem 内核对象子系统
   subsysten是kset的集合,对应于sysfs的根目录,block_subsys->block;devices_subsys->devices;
  struct subsystem 对各个kset中的subsys域设置到subsystem可以将kset加入到同一个subsystem,他们共享一个rw_sem,用于访问kset中的链表.
  {
   struct kset kset;   内嵌kset对象
   struct rw_semaphore rwsem;  互斥信号量
  }
 void subsystem_init( struct subsystem *subsys );
 int subsystem_register( struct subsystem *subsys );
 void subsystem_unregister( struct subsystem *subsys );
 struct subsytem *sub_sys_get( struct subsystem *subsys );
 void subsys_put( struct subssytem *subsys ); 

  4,linux设备模型组件,device,device_driver,bus_type,class,class_device
 struct device
 {
  struct klist klist_children;  设备列表中的子列表
  struct klist_node knode_parent;  兄弟节点
  struct klist_node knode_driver;  驱动节点
  struct klist_node knode_bus;  总线节点
  struct device *parent;   父设备

  struct kobject kobj;    内嵌的kobject内核对象
  charbus_id[BUS_ID_SIZE];   在总线上的位置
  struct device_attribute uevent_attr;

  struct semaphore sem;

  struct bus_type *bus;   总线
  struct device_driver *driver;  驱动程序
  void *driver_data;    驱动的私有数据
  void *platform_data;    平台特定的数据
  void *firmware_data;    固件特定的数据(ACPI,BIOS数据)
  struct dev_pm_info power;   

  u64 *dmamask     DMA掩码
  u64 coherent_dma_mask;   
  struct list_head dma_pools;   DMA缓冲池
  struct dma_coherent_mem *dma_mem;

  void (*release)(struct device *dev); 释放设备的方法
 };
 device结构体描述 设备的信息,层次结构,以及设备与总线的关系.用于其它大的结构体中(struct pci_dev)
 device_register()  将一个新的device对象插入设备模型,并在/sys/devices下创建一个目录.
 device_unregister()
 get_device()
 put_device()

 struct device_driver
 {
 }
 内嵌kobject实现引用计数和层次管理,get_driver(),put_driver().driver_register()向设备模型插入新的driver对象.对应sysfs文件.
 还包括几个函数:处理 探测,移除,电源管理事件.

 struct bus_type
 {
 }
 内嵌bus_subsys对象实现总线类型的管理.每个bus_type对应sysfs里的/sys/bus/pci等,里面的2个子目录devices和drivers(对应2个域).
 包括几个函数:处理 热插拔,即插即拔,电源管理事件.

 struct class
 {
 }
 表示某一类设备,/sys/class,内含一个class_device(表示逻辑设备)链表,class_device里的dev成员连接到物理设备.
 也包括几个函数:处理 热插拔,即插即拔,电源管理事件.

 struct class_device
 {
 }
 int class_register( struct class *cls );
 void class_unregister( struct class *cls );
 int class_device_register( struct class_device *class_dev );
 void class_device_unregister( struct class_device *class_dev );
 当设备加入或离开设备模型时,class_interface中的设备成员函数被调用.
 struct class_interface
 {
 }

  5, 属性
 bus,device,driver,class模型里都有属性结构.
 struct bus_attribute
 {
  struct attribute attr;
  ssize_t (*show)( struct bus_type *, char * buf );
  ssize_t (*store)( struct bus_type *, const char *buf, size_t count );
 };
 struct driver_attribute
 {
 };
 struct class_attribute
 {
 };
 struct class_device_attribute
 {
 };
 有一组宏用于创建和初始化bus_attribute.
 有一组函数用于添加和删除bus_attribute.

2.4的devfs设备文件系统
 使得设备驱动程序能自主管理它的设备驱动文件.优点:
 1,在设备初始化时在/dev下创建文件,卸载设备时删除.
 2,驱动程序可以指定设备名,所有者和权限位.用户空间可以修改所有者和权限位.
 3,register_chrdev()传递0主设备号可以动态获得主设备号,devfs_register()中指定次设备号. 
 devfs_handle_t devfs_mk_dir( devfs_handle_t dir, const char *name, void *info );  创建设备目录
 devfs_handle_t devfs_register( devfs_handle_t dir, const char *name, unsigned int flags,创建设备文件
         unsigned int major, unsigned int minor, umode_t mode,
         void *ops, void *info );
 void devfs_unregister( devfs_handle_t de );       撤消设备文件
实例:
 static devfs_handle_t devfs_handle;
 static int __init xxx_init( void )
 {
  int ret;
  int i;
  ret = register_chrdev( XXX_MAJOR, DEVICE_NAME, &xxx_fops );    在内核中注销设备
  if ( ret < 0 )
  {
   printk( DEVICE_NAME " can't register major number/n");
   return ret;
  }
  devfs_handle = devfs_register( NULL, DEVICE_NAME, DEVFS_FL_DEFAULT, XXX_MAJOR, 0, 创建设备文件
          S_IFCHR | S_IRUSR | S_IWUSR, &xxx_fops, NULL );
  ...
  printk( DEVICE_NAME " initialized/n");
  return 0;
 }
 
 static void __exit xxx_exit(void)
 {
  devfs_unregister( devfs_handle );       撤消设备文件
  unregister_chardev( XXX_MAJOR, DEVICE_NAME );     注销设备
 }
 module_init( xxx_init );
 module_exit( xxx_exit );

2.6的udev设备文件系统
 devfs现在不太使用,因为udev的在用户态利用sysfs中的信息 定义规则 并提取主次设备号来动态创建/dev设备文件节点.
udev的组成
 设计目标:
  1,在用户空间执行;
  2,动态建立/删除设备文件;
  3,不关心主/次设备号;
  4,提供LSB标准的名称;
  5,也可提供固定名称
 udev分成3个固定的子系统: namedev命名子系统,
     libsysfs提供访问sysfs并获取信息的标准接口,
     udev提供/dev设备节点文件的动态创建和删除策略.
 udev部分承担与namedev和libsysfs库交互的任务.当/sbin/hotplug程序被内核调用时,udev将运行,
 工作过程如下:
 1,当内核检测到新设备后,内核会在sysfs中生成新的记录并导出设备信息及所发生的事件.
 2,udev获取信息,调用namedev命名,调用libsysfs指定主/次设备号,并创建设备文件.设备移出时将/dev文件删除.

 namedev通过5个过程来判定设备的命名:
 1,标签label/序号serial:检查设备是否有唯一的识别记号.
  # USB Epson printer to be called lp_epson
  LABEL, BUS="usb", serial="HXOLL0012202323480", NAME="lp_epson"
  # USB HP printer to be called lp_hp
  LABEL, BUS="usb", serial="W09090207101241330", NAME="lp_hp"
 2,设备总线号:检查总线的设备编号.
  # sound card with PCI bus id 00:0b.0 to be the first sound card
  NUMBER, BUS="pci", id="00:0b.0", NAME="dsp"
  # sound card with PCI bus id 00:07.1 to be the second sound card
  NUMBER, BUS="pci", id="00:07.1", NAME="dsp1"
 3,总线上的拓扑:检查设备在总线上的位置.
  #USB mouse plugged into the third port of the first hub to be called mouse0
  TOPOLOGY, BUS="usb", place="1.3", NAME="mouse0"
  #USB tablet plugged into the second port of the second hub to be called mouse1
  TOPOLOGY, BUS="usb", place="2.2", NAME="mouse1"
 4,替换名称:检查导出的名称匹配替代的字符串时,就会替代指定的名称.
  # ttyUSB1 should always be called visor
  REPLACE, KERNEL="ttyUSB1", NAME="visor"
 5, 内核提供的名称:如果以上都不符合,用缺省名称.

udev的规则文件
 以#为注释,其它每行为规则,由匹配和赋值组成.
 匹配: ACTION  匹配行为
  KERNEL  匹配内核设备名
  BUS  匹配总线类型
  SYSFS  匹配从sysfs得到的信息,label,vendor,USB序列号
  SUBSYSTEM 匹配子系统名
 赋值: NAME  创建设备文件名
  SYMLINK 创建链接名 
  OWNER  设置设备所有者
  GROUP  设置设备的组
  IMPORT  调用外部程序
 例1: SUBSYSTEM=="net", ACTION=="add", SYSFS{address}=="00:0d:87:f6:59:f3", IMPORT="/sbin/rename_netiface %k eth0"
  当系统出现新的硬件,其subsys==net,系统对该新硬件采取的action=add,且此硬件在sysfs中的address为 "00:0d:87:f6:59:f3"时:
  udev层次的动作为 import=/sbin/rename_netiface. 2个参数;一个%k(kernel对该设备定义的名称),一个 eth0.
 例2: BUS="usb",  SYSFS{serial}="HX0LL0012202323480", NAME="lp_epson",  SYNLINK="printers/epson_stylus"
  udev的固定命名,可根据序列号,devfs不能.
 udevinfo -a -p /sys/block/sda 可以查找编写udev规则文件的有用信息.

创建和配置udev
 1, tar zxvf udev-114.tar.gz 命令解压
 2, make, 产生 test-udev; udevcontrol; udevd; udevinfo; udevmonitor; udevsettle; udevstart; udevtest; udevtrigger工具
 3, copy到/sbin, 把解压udev-114.tar.gz后的etc目录复制到/etc下, /etc/udev/udev.conf; /etc/udev/rules.d
 4, 编写 启动,停止,重新启动 等 udev脚本.
 udev运行脚本:
  ...
 udevtest  /sys/class/tty/ttys0  可以用来测试udev对该设备所采取的行动, 用来帮助 udev进行规则文件的调试.

三,c_dev
cdev 结构体
 struct cdev
 {
  struct kobject kobj;   内嵌的kobject对象
  struct module *owner;  所属模块
  struct file_operations *ops; 文件操作结构体 
  struct list_head list;  
  dev_t dev;    设备号, MAJOR(dev_t dev); MINOR(dev_t dev); MKDEV(int major, int minor)
  unsigned int count;
 }
 void cdev_init( struct cdev *cdev, struct file_operations *fops )
 {
  memset( cdev, 0, sizeof *cdev );
  INIT_LIST_HEAD( &cdev->list );
  cdev->kobj.ktype = &ktype_cdev_default;
  kobject_init( &cdev->kobj );
  cdev->ops = fops;   将传入的文件操作结构体赋值给cdev的ops.
 }
 struct cdev *cdev_alloc( void )  动态申请一个cdev内存
 {
  struct cdev *p=kmalloc( sizeof(struct cdev), GFP_KERNEL); 分配cdev内存
  if {p} {
   memset( p, 0, sizeof(struct cdev) );
   p->kobj.ktype = &ktype_cdev_dynamic;
   INIT_LIST_HEAD( &p->list );
   kocject_init( &p->kobj );
  }
  return p;
 }
 void cdev_put( struct cdev *p );
 int cdev_add( struct cdev *, dev_t, unsigned ); 向系统添加和删除一个cdev
 void cdev_del( struct cdev * ); 

分配和释放设备号
 在cdev_add()添加cdev之前要先申请设备号
 int register_chadev_region( dev_t from, unsigned count, const char *name ); 用于已知设备号的情况
 int alloc_chrdev_region( dev_t *dev, unsigned baseminor, unsigned count, const char *name ); 动态分配
 在cdev_del()之后再释放申请的设备号
 void unregister_chrdev_region( dev_t from, unsigned count );

file_operations结构体
 struct file_operations
 {
  struct module *owner;         拥有该结构的指针, THIS_MODULES
  loff_t ( *llseek )( struct file *, loff_t, int );     修改文件当前的读写位置,出错为负
  ssize_t ( *read )( struct file *, char __user *, size_t, loff_t * );  从设备同步读取数据
  ssize_t ( *aio_read )( struct kiocb *, char __user *, size_t, loff_t );  初始化一个异步读操作
  ssize_t ( *write )( struct file *, const __user *, size_t, loff_t * );  向设备发送数据, -EINVAL
  ssize_t ( *aio_write )( struct kiocb *, const char __user *, size_t, loff_t ); 初始化一个异步写操作
  int (*readdir)( struct file *, void *, filldir_t );     读取目录,设备文件的字段为NULL
  unsigned int ( *poll )( struct file *, struct poll_table_struct * );  轮询操作,判断是否可以进行非阻塞的读写 
  int ( *ioctl )( struct inode *, struct file *, unsigned int, unsigned long ); 执行设备I/O控制命令, -EINVAL
  long ( *unlocked_ioctl )( struct file *, unsigned int , unsinged long );  不使用BLK文件系统,将使用此种函数指针代替ioctl
  long ( *compat_ioctl )( struct file *, unsigned int, unsigend long );  在64位系统里,32位的ioctl将用这个函数指针.
  int ( *mmap )( struct file *, struct vm_area_struct * );    请求将设备内存映射到用户进程空间. -ENODEV
  int ( *open )( struct inode *, struct file * );     打开
  int ( *flush )( struct file * );        
  int ( *release )( struct inode *, struct file * );     关闭
  int ( *synch )( struct file *, struct dentry *, int datasync );   刷新数据
  int ( *aio_fsync )( struct kiocb *, int datasync );     异步 fsync
  int ( *fasync )( int, struct fiel *, int );      通知设备 FASYNC 标志发生变化
  int ( *lock )( struct file *, int, struct file_lock * );    
  ssize_t ( *readv )( struct file *, const struct iovec *, unsigned long, loff_t * ); 分散/聚集形的读操作
  ssize_t ( *writev )( struct file *, const struct iovec *, unsigned long, loff_t * ); 分散/聚集形的写操作
  ssize_t ( *sendfile )( struct file *, loff_t *, size_t, read_actor_t, void * );  NULL
  ssize_t ( *sendpage )( struct file *, struct page *, int, size_t, loff_t *, int ); NULL 
  unsigned long ( *get_unmapped_area )( struct file *, unsigned long, unsigned long, unsigned long, unsigned long );
             在用户进程空间找一个可以映射设备内存空间的位置.
  int ( *check_flags )( int );       允许模块检查传递给 fcntl(F_SETFL...) 调用的标志
  int ( *dir_notify )( struct file *filp, unsigned long arg );  
  int ( *flock )( struct file *, int, struct file_lock * );
 } 

linux字符设备驱动的组成
   1, 字符设备驱动模块加载与卸载.
 struct xxx_dev_t
 {
  struct cdev cdev;
  ...
 }xxx_dev;         设备结构体
 static int __init xxx_init( void )
 {
  ...
  cdev_init( &xxx.cdev, &xxx_fops );
  xxx_dev.cdev.owner = THIS_MODULE;
  if ( xxx_major )       获得字符设备号
  {
   register_chrdev_region( xxx_dev_no, 1, DEV_NAME );
  }
  else
  {
   alloc_chrdrv_region( &xxx_dev_no, 0, 1, DEV_NAME );
  }
  ret = cdev_add( &xxx_dev.cdev, xxx_dev_no, 1 );   注册设备
  ...
 }
 static void __exit xxx_exit( void )
 {
  unregister_chrdev_region( xxx_dev_no, 1 );
  cdev_del( &xxx_dev.cdev );      注销设备
  ...
 }

   2, 字符设备驱动的file_operations中的成员函数.
 ssize_t xxx_read( struct file &filp, char __user *buf, size_t count, loff_t *f_pos )
 {
  ...
  copy_to_user(buf, ..., ... );   get_user( val, (int*)arg );
  ...
 }
 
 ssize_t xxx_write( struct file *filp, const char __user *buf, size_t count, loff_t *f_pos )
 {
  ...
  copy_from_user( ..., buf, ... );   put_user(val, (int*)arg );
  ...
 }
 int xxx_ioctl( struct inode, struct file *filp, unsigned int cmd, unsigned long arg )
 {
  ...
  switch ( cmd )
  {
   case XXX_CMD1:
   break;
   case XXX_CMD2:
   break;
   default:
   return - ENOTTY;   
  }
  return 0;
 }
 struct file_operations xxx_ops =
 {
  .owner = THIS_MODULE,
  .read = xxx_read,
  .write = xxx_write,
  .ioctl = xxx_ioctl,
  ...
 };
 
globalmem设备驱动实例
  1,头文件,宏及设备结构体
  2,加载和卸载设备驱动
  3,读写函数
  4,seek()函数
  5,ioctl()函数
  6,使用文件私有数据
globalmem驱动在用户空间的验证

四,block_dev
块设备的IO操作特点
 块设备需要buffer和块顺序操作.
块设备驱动结构
  block_device_operations
 struct block_device_operations
 {
  int ( *open )( struct inode *, struct file * );     打开设备时调用
  int ( *release )( struct inode *, struct file * );    释放设备
  int ( *ioctl )( struct inode *, struct file *, unsigned, unsigned long ); ioctl系统调用的实现
  int ( *unlocked_ioctl )( struct file *, unsigned, unsigned long );  
  long ( *compact_ioctl )( struct file *, unsigned, usngiend long );
  int ( *direct_access )( struct block_device *, sector_t, unsigned long * ); 
  int ( *media_changed )( struct gendisk *);     检查介质被改变变量
  int ( *revalidate_disk )( struct gendisk );     介质改变后调用,给驱动一个机会使新介质准备好
  int ( *getgeo )( struct block_device *, struct hd_geometry * );  填充驱动器信息(hd_geometry结构,磁头,扇面,柱面)
  struct module *owner;        一个模块指针,指向这个结构体.THIS_MODULE
 }
 int ( *open )( struct inode *inode, struct file *filp );
 int ( *release )( struct inode *inode, struct file *filp );
 int ( *ioctl )( struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg );
 int ( *madie_changed )( struct gendisk *gd );
 int ( *revalidate_disk )( struct gendisk *gd );
 int ( *getgeo )( struct block_device *, struct hd_geometry * );

  gendisk(通用磁盘),用来表示1个独立的磁盘设备或分区.
 struct gendisk
 {
  int major;      磁盘主设备号
  int first_minor;     磁盘次设备主号
  int minors;      磁盘次设备号
  char disk_name[32];     设备名称
  struct hd_struct **part;    磁盘上的分区信息
  struct block_device_operations *fops;  块设备操作结构体
  struct request_queue *queue;   请求队列
  void private_data;     私有数据
  sector_t capacity;     扇区数,512字节为一个扇区

  int flags;      
  char devfs_name;
  int number;
  struct device *driverfs_dev;
  struct kobject kobj;

  struct timer_rand_state *random;
  int policy;

  atomic_t sync_io;
  unsigned long stamp;
  int flight;
  #ifdef CONFIG_SMP
   struct disk_stats *dkstats;
  #else
   struct disk_stats dkstats;
  #endif
 };
 struct gendisk *alloc_disk( int minors );
 void add_disk( struct gendisk *gd );
 void del_gendisk( struct gendisk *gd );
 void set_capacity( struct gendisk *disk, sector_t size ); 设置gendisk的容量,内核和块驱动交互的扇区是512byte.CDROM物理扇区是2KB

  request(请求),request_queue(请求队列)和bio(块IO)
     1,struct request
 {
  struct list_head queuelist;   用于链接这个请求到请求队列的链表结构,
        blkdev_dequeue_request()用于从队列移除请求.
        rq_data_dir( struct request *req)从req获得数据传输的方向,0从设备读,1从设备写.
  unsigned long flags;    REQ
 
  sector_t sector;    要传送的下一个扇区,驱动使用这3个成员.
  unsigned long nr_sectors;   要传送的扇区数目
  unsigned int current_nr_sectors;  当前要传送的扇区数目

  sector_t hard_sector;   还未完成的,需要完成的下一个扇区,处于块设备层,驱动不应当使用.
  unsigned long hard_nr_sectors;  需要被完成的扇区数目
  unsigned int hard_cur_sectors; 当前IO操作中待完成的扇区数目

  struct bio *bio;    本请求的 bio 结构体链表
  struct bio *biotail;    本请求的 bio 结构体链表尾

  void *elevator_private;   

  unsigned short ioprio;

  int rq_status;
  struct gendisk *rq_disk;
  int errors;
  unsigned long start_time;

  unsigned short nr_phys_segments;  本请求在物理内存中占用的段的数目,如果设备支持SG(scatter/gather,分散/聚集)操作,可申请
        sizeof(scatterlis)*nr_phys_segments大小的内存,并进行DMA映射.
       int blk_request_map_sg( request_queue_t *q, struct request *req, struct scatterlist *sqlist);
           dma_map_sg();

  unsigned short nr_hw_segments;  与nr_phys_segments相同,但考虑了系统I/O MMU的remap.
  int tag;
  char *buffer;     传送的缓冲区地址,内核虚拟地址.

  int ref_count;    引用计数.
  ...
 }
     2,struct request_queue
 {
  ...
  spinlock_t __queue_lock;   保护队列结构体的自旋锁
  spinlock_t *queue_lock;   
  struct kobject kobj;    自带的kobject
  unsigned long nr_requests;   最大的请求数量
  unsigned int nr_congestion_on;
  unsigned int nr_congestion_off;
  unsigned int nr_batching;
  unsigned short max_sectors;   最大的扇区数
  unsigned short max_hw_sectors;
  unsigned short max_phys_segments;  最大段数
  unsigned short mex_hw_segments;
  unsigned short hardset_size;  硬件扇区尺寸
  unsigned int max_segmnet_size;  最大的段尺寸
  unsigned long seg_boundary_mask;  段边界掩码
  unsigned int dma_alignment;   DMA传送的内存对齐限制
  struct blk_queue_tag *queue_tags;
  atomic_t refcnt;    引用计数
  unsigned int in_flight;
  unsigned int sg_timeout;
  unsigned int sg_reserved_size;
  int node;
  struct list_head drain_list;
  struct request *flush_rq;
  unsigned char ordered;
 }
 请求队列存储了设备可以支持的请求的类型,请求的最大数,请求里的最大段数,硬件扇区大小,对齐要求等参数信息.
 其结果是如果配置正确,它不会交给设备一个不能处理的请求.
 请求队列里有一个插入接口,这个接口允许使用多个IO调度器(elevator).电梯:实现排序合并读写
 Noop I/O schedule:  只合并,排序
  Anticipatory I/O schedule: 默认的,速度快,但体积大,数据库慢.
 Deadline I/O schedule: 比Anticipatory小 
 CFQ I/O schedule:  所有任务相同带宽,mplayer,xmms效果好.
 可以在kernel添加启动参数,改变IO调度方法.
 kernel elevator=deadline
 
 1,初始话请求队列,有内存分配,检查返回值.驱动初始化中调用. 
  request_queue_t *blk_init_queue( request_fn_proc *rfn, spinlock_t *lock );
   request_fn_proc:  request的处理函数的指针
   spinlock:   控制访问队列权限的自旋锁
 2,清除请求队列,驱动卸载时调用. 
  void blk_cleanup_queue( request_queue_t *q );
  #define blk_put_queue(q) blk_cleanup_queue((q))
 3,分配"请求队列"  
  request_queue_t *blk_alloc_queue( int gfp_mask );
  void blk_queue_make_request( request_queu_t *q, make_request_fn *mfn ); 绑定"请求队列"和制造请求的函数
 4,提取请求,标识为活动的请求.  
  struct request *elv_next_request( request_queue_t *queue );
 5,去除请求  
  void blk_drv_dequeue_request( struct request *req );
  void elv_requeue_request( request_queue_t *q, struct request *req ); 插入request
 6,启停请求队列  
  void blk_start_queue( request_queue_t *queue );
  void blk_stop_queue( request_queue_t *queue );
 7,参数设置
  void blk_queue_max_sectors( request_queue_t *queue, unsigned short max ); request的最大扇区数,255   
  void blk_queue_max_segments( request_queue_t *queue, unsigned short max ); request的最大段(不相邻的区)数目,128
  void blk_queue_max_hw_segments( request_queue_t *queue, unsigned short max ); ?
  void blk_queue_max_segment_size( request_queue_t *queue, unsigned int max ); request的段的最大字节数,65536
 8,通告内核
  void blk_queue_bounce_limit( request_queue_t *q, u64 dma_addr );
    通告内核块设备DMA时的最高地址,如果某request里超过这个dam_addr,系统会自动分配一个"反弹"缓冲区,代价昂贵.
   BLK_BOUNCE_HIGH: 对高端内存页使用"反弹"缓冲区,缺省值.
   BLK_BOUNCE_ISA: 在16MB的ISA区执行DMA
   BLK_BOUNCE_ANY: 任何地址
  void blk_queue_segment_boundary( request_queue_t *q, unsigned long mask );
    通告内核,驱动处理的最大内存尺寸.mask缺省0xfffffff
  void blk_queue_dma_alignment( request_queue_t *queue, int mask );
    通告内核,块设备DMA时的内存对齐限制,缺省0x1ff(512Byte)    
  void blk_queue_hardset_size( request_queue_t *queue, unsigned short max );
    通告内核,块设备扇区大小,内核的request应该是max的倍数或对界,但是内核块设备层与驱动间通信还是以512Byte的扇区为单位.

     3,bio(块IO),通常一个bio对应一个I/O请求
  struct bio
  {
   sector_t bi_sector;    要传输的第一个扇区,512Byte
   struct bio *bi_next;    下一个bio
   struct block_sevice;    bi_bdev
   unsigned long bi_flags;   状态,命令等,bio_data_dir(bio)宏获得读写方向
   unsigned long bi_rw;    低位代表读写,高为代表优先级

   unsigned short bi_vcnt;   bio_vec数量
   unsigned short bi_idx;   当前bvl_vec索引

   unsigned short bi_phys_segments;  不相邻的物理段数目

   unsigned short bi_hw_segments;  物理合并和DMA remap后不相邻的物理段数目

   unsigned int bi_size;   以字节为单位的所需传输的数据大小

   unsigned int bi_hw_front_size;  
   unsigned int bi_hw_back_size;

   unsigned int bi_max_vecs;   最大的bvl_vec数

   stuct bio_vec *bi_io_vec;   实际的vec列表,struct bio_vec
           { struct page *bv_page;  页指针
             unsigned int bv_len; 传输的字节数 
             unsigned int bv_offset; 偏移位置 
           }
           bio_for_each_segments( bvl, bio, i, start_idx )
   bio_end_io_t *bi_end_io;
   atomic_t bi_cnt;

   void *bi_private;

   bio_destructor_t *bi_destructor;  destructor
  };
  int bio_data_dir( struct bio *bio );  获得数据传输方向
  struct page *bio_page( struct bio *bio ); 获得目前的页指针    
  int bio_offset( struct bio *bio );  返回操作对应的当前页内的偏移
  int bio_cur_sectors( struct bio *bio ); 返回当前bio_vec要传输的扇区数    
  char *bio_data( struct bio *bio );  返回数据缓冲区的内核虚拟地址     
  char *bvec_kmap_irq( struct bio_vec *bvec, unsigned long *flags ); 
         返回一个内核虚拟地址,这个地址用于存取被给定的bio_vec入口指向的数据缓冲区
         它也会屏蔽中断并返回一个原子kmap,因此,在unmap前,驱动不应该睡眠.
  void bvec_kunmap_irq( char *buffer, unsigned long *flags );
  char *bio_kmap_irq( struct bio *bio, unsigned long *flags ); 对bvec_kmap_irq的包装,返回给定bio的当前bio_vec入口的映射
  char *__bio_kmap_atomic( struct bio *bio, int i, enum km_type type ); 返回指定bio的第i个缓冲区的虚拟地址
  void __bio_kunmap_atomic( char *addr, enum km_type type ); 
  void bio_get( struct bio *bio );   引用bio
  void bio_put( struct bio *bio );   释放对bio的引用

  块设备驱动注册与注销
  int register_blkdev( unsigned int major, const char *name ); 在/proc/devices显示
  int unregister_blkdev( unsigned int major, const char *name );
  xxx_major = register_blkdev( xxx_major, "xxx" );
  if ( xxx_major < 0 )
  {
   printk( KERN_WARNING " xxx: unable to get major number/n " );
   return -EBUSY;
  }

块设备的模块加载与卸载
 加载步骤: 1,分配,初始化请求队列; 绑定请求队列和请求函数.
   2,分配,初始化gendisk; 给gendisk的major, fops, queue等成员赋值; 最后添加gendisk.
   3,注册块设备驱动
  static int __init xxx_init( void )
  {
   xxx_disks = alloc_disk( 1 );   分配gendisk
   if ( !xxx_disk ) 
   {
    goto out;
   }
   if( register_blkdev( XXX_MAJOR, "xxx" ) ) 块设备注册
   {
    err = -EIO;
    goto out;
   }
   
   xxx_queue = blk_alloc_queue( GFP_KERNEL ); 分配"请求队列"
   if ( !xxx_queue )
   {
    goto out_queue;
   }
   blk_queue_make_request( xxx_queue, &xxx_make_request ); 绑定"制造请求"函数 对应无 队列IO处理
   blk_queue_hardsect_size( xxx_queue, xxx_blocksize ); 硬盘扇区尺寸设置
   
  或: xxx_queue = blk_init_queue( xxx_request, xxx_lock );     对应有队列IO处理
   
   xxx_disks->major = XXX_MAJOR;
   xxx_disks->first_minor = 0;
   xxx_disks->fops = *xxx_op;
   xxx_disks->queue = xxx_queue;
   sprintf( xxx_disks->disk_name, "xxx%d", i );
   set_capacity( xxx_disks, xxx_size );  gendisk容量
   add_disk( xxx_disks );    添加gendisk 
   return 0;
    out_queue: unregister_blkdev( XXX_MAJOR, "xxx" );
    out: put_disk( xxx_disks );
   blk_cleanup_queue( xxx_queue );
   return -ENOMEM;
  }

 卸载步骤: 1,清除请求队列
   2,删除gendisk和gendisk引用
   3,删除块设备引用,注销块设备驱动.
  struct void __init xxx_exit( void )
  {
   if ( bdev )
   {
    invalidate_bdev( xxx_bdev, 1 );
    blkdev_put( xxx_bdev );
   }
   del_gendisk( xxx_disks );
   put_disk( xxx_disks );
   blk_cleanup_queue( xxx_queue[i] );
   unregister_blkdev( XXX_MAJOR, "xxx" );
  }

块设备打开与释放,open,release
 static int xxx_open( struct inode *inode, struct file *filp )
 {
  struct xxx_dev *dev = inode->i_bdev->bd_disk->private_data;
  filp->private_data = dev;     赋值file的private_data
  ...
  return 0;
 }

块设备的ioctl,块设备层处理了大部分的设备I/O控制.
 int xxx_ioctl( struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg )
 {
  long size;
  struct hd_geometry geo;
  struct xx_dev *dev = filp->private_data;
  switch ( cmd )
  {
   case HDIO_GETGEO:
    size = dev->size*( hardsect_size/KERNEL_SECTOR_SIZE );
    geo.cylinders = ( size & ~0x3f ) >> 6;
    geo.heads = 4;
    geo.sectors = 16;
    geo.start = 4;
    if( copt_to_user( (void __user*)arg, &geo, sizeof(geo) ) )
    {
     return -EFAULT;
    }
    return 0;
  }
  return -ENOTTY ;
 }

块设备的I/O请求处理
  使用请求队列,对于磁盘有用,通过优化读写顺序.但对RAMDISK,FLASH无用.
    1,单个请求 
 void request( reqeust_queue_t *queue ); 当内核要对块设备读写时,调用驱动里的这个函数.
 static void xxx_request( request_queue_t *q )
 {
  struct request *req;
  while ( req = evl_next_request(q) != NULL )    获得第一个request
  {
   struct xxx_dev *dev = req->rq_disk->private_data;
   if ( !blk_fs_request( req ) )     不是文件系统的request
   {
    printk( KERL_NOTICE " Skip non-fs request/n" );
    end_request( req, 0 );     通知request失败,0
    continue;
   }
   xxx_transfer( dev, req->sector, req->currnet_nr_sectors, req->buffer, rq_data_dir(req) );
   rq_data_dir( req );       处理这个request
   end_request( req, 1 );      通知完成这个request,1
  }
 }
 static xxx_transfer( struct xxx_dev *dev, unsigned long sector, unsigned long nsect, char *buffer, int write )
 {           完成具体的块设备IO操作
  unsigned long offset = sector * KERNEL_SECTOR_SIZE;
  unsigned long nbytes = nsect * KERNEL_SECTOR_SIZE;
  if ( offset+nbytes > dev->size )
  {
   printk( KERNER_NOTICE "Beyond-end write (%1d %1d)/n ", offset, nbytes );
   return;
  }
  if ( write )
  {
   write_dev( offset, buffer, nbytes );    向设备写nbytes个数据
  }
  else
  {
   read_dev( offset, buffer, nbytes );    从设备读nbytes个数据
  }
 }
 void end_request( struct request *req, int uptodate )
 {
  if ( !end_that_request_first( req, uptodate, req->hard_cur_sectors ) )
  {        当设备完成一个IO请求的部分或全部传输后,必须通告设备层.使用
        int end_that_request_first( struct request *req, int success, int count );
          返回0表示count个sectors已传送.
   add_disk_randomness( req->rq_disk );  当disk操作是随机时(机械式),用这个函数,块IO的定时来给系统做随机数池 贡献熵.
   blkdev_dequeue_request( req );   从队列清除req
   end_that_request_last( req );   通知所有正在等待这个req完成的对象,req已完成并回收这个struct 
  }
 }
  2,遍历所有请求,遍历请求里每个bio,bio里每个segments.
 static void xxx_full_request( request_queue_t *q )
 {
  struct request *req;
  int sectors_xferred;
  struct xxx_dev *dev = q->queuedate;
  while( (req = elv_next_request(q) ) != NULL )
  {
   if ( !blk_fs_request(req) )
   {
    printk( KERN_NOTICE " Skip non-fs request/n" );
    end_request( req, 0 );
    continiue;
   }
   sectors_xferred = xxx_xfer_request( dev, req );
   if ( !end_that_request_first( req, 1, sectors_xferred ) )
   {
    blkdev_dequeue_request( req );
    end_that_request_last( req );
   }
  }
 }
 static int xxx_xfer_request( struct xxx_dev *dev, struct request *req )
 {
  struct bio *bio;
  int nsect = 0;
  rq_for_each_bio( bio, req )
  {
   xxx_xfer_bio( dev, bio );
   nsect += bio->bi_size / KERNEL_SECTRO_SIZE;
  }
  return nsect;
 }
 static int xxx_xfer_bio( struct xxx_dev *dev, struct bio *bio )
 {
  int i;
  struct bio_vec *bvec;
  sector_t sector = bio->bi_sector;

  bio_for_each_segment( bvec, bio, i )
  {
   char *buffer = __bio_kmap_atomic( bio, 1, KM_USER0 );
   xxx_transfer( dev, sector, bio_cur_sectors(bio), buffer, bio_data_dir(bio) == WRITE );
   sector += bio_cur_sectors( bio );
   __bio_kunmap_atomic( bio, KM_USER0 );
  }
  return 0;
 }


  不使用请求队列,RAMDISK,SD RAM
 static xxx_make_request( request_queue_t *q, struct bio *bio ) q:"请求队列" "制造请求"函数
 {
  struct xxx_dev *dev = q->queuedata;
  int status;
  status = xxx_xfer_bio( dev, bio );   处理bio
  bio_endio( bio, bio->bi_size, status );  通告结束
  return 0;      应该是0,如果不为0,bio将再次提交
 }
 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值