一,内核模块
模块可以控制内核的大小,一旦被加载,它和内核中其它功能一样.
用户可以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将再次提交
}