本文共 31197 字,大约阅读时间需要 103 分钟。
一,内核模块
模块可以控制内核的大小,一旦被加载,它和内核中其它功能一样. 用户可以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 fi3,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物理扇区是2KBrequest(请求),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将再次提交 }
转载地址:http://iucqi.baihongyu.com/