这篇是2年前写的东西,有不少需要改进的地方;暂且记录下来,在以后的文章中来改进之。:D
本文将从multiple devie driver的加载、卸载、ioctl系统调用的实现等线索,对md驱动中的具体实现加以讨论。
另外,本文将从md驱动中内核线程的使用方面进行一些讨论,借以从更加小粒度的地方了解md driver的具体实现。
在md.c的代码中,还涉及到md随系统启动的管理、与/proc文件系统的交互等等细节,在以下适当地地方将给出讨论。
本文所讨论的linux kernel为Redhat Linux AS 4.0 所使用的版本2.6.9。其中所涉及到的一些部分,可能和其他linux kernel版本中的md driver有所出入,请注意。
文中深红色加粗字体所表示的是对代码的注释,青色加粗的字体表示的是代码。
1 module加载、卸载
在module加载时,将调用md_init函数;在卸载时,将调用md_exit函数。md_init的原形如下:
int __init md_init(void)
{
……
if (register_blkdev(MAJOR_NR, "md"))/*register md block device. The major is number 9 with name 'md' have unpartitioned md arrays, one per minor number. */
return -1;
if ((mdp_major=register_blkdev(0, "mdp"))<=0) {
unregister_blkdev(MAJOR_NR, "md");
return -1;
}/*this sector register mdp device. The major is allocated dynamically with name 'mdp' and had on array for every 64 minors, allowing for upto 63 partitions.
devfs_mk_dir("md");/* Create a directory in the devfs namespace.*/
blk_register_region(MKDEV(MAJOR_NR, 0), MAX_MD_DEVS, THIS_MODULE,
md_probe, NULL, NULL);/*regist “md” device number renges*/
blk_register_region(MKDEV(mdp_major, 0), MAX_MD_DEVS<
/*blk_register_region() is under the gendisk interface*/
for (minor=0; minor < MAX_MD_DEVS; ++minor)
devfs_mk_bdev(MKDEV(MAJOR_NR, minor),
S_IFBLK|S_IRUSR|S_IWUSR,
"md/%d", minor);
/*create all devices under dir md*/
for (minor=0; minor < MAX_MD_DEVS; ++minor)
devfs_mk_bdev(MKDEV(mdp_major, minor<
S_IFBLK|S_IRUSR|S_IWUSR,
"md/d%d", minor);
/*create all devices under dir md*/
register_reboot_notifier(&md_notifier);/*register notifier of reboot or shutdown*/
raid_table_header = register_sysctl_table(raid_root_table, 1);
md_geninit();
return (0);
}
md_init在模块加载时注册md设备以及相关的系统调用接口。在Linux kernel 2.6.9中,md driver在加载时,注册两个主设备号(区别于Linux kernel 2.4中的使用一个主设备号),在以上代码注释中给出解释。
其中,md_geninit()主要注册md中/proc文件系统的接口,其原形如下:
static void md_geninit(void)
{
……
p = create_proc_entry("mdstat", S_IRUGO, NULL);/*register proc entry*/
if (p)
p->proc_fops = &md_seq_fops;/* set proc filesystem operator*/
}
在module卸载的时候调用md_exit函数,释放一些资源,其原形如下:
static __exit void md_exit(void)
与module加载时调用的md_init()函数对应,md_exit()函数解除md和mdp的注册、解除所有md设备在devfs中的注册、解除reboot notifier、解除系统调用接口和proc文件系统的接口。以上,没有在md_exit()的代码中给出注释,这些可以参照md_init()函数中的注释,对比来看。
2 ioctl系统调用
在md_u.h中定义的ioctl command如下:
#define RAID_VERSION _IOR (MD_MAJOR, 0x10, mdu_version_t)
#define GET_ARRAY_INFO _IOR (MD_MAJOR, 0x11, mdu_array_info_t)
#define GET_DISK_INFO _IOR (MD_MAJOR, 0x12, mdu_disk_info_t)
#define PRINT_RAID_DEBUG _IO (MD_MAJOR, 0x13)
#define RAID_AUTORUN _IO (MD_MAJOR, 0x14)
以上ioctl command获取multiple device的状态信息。这些信息包括,RAID的level值、mdu_array_info_s结构形式的diskarray信息、mdu_disk_info_s结构形式的disk信息等。
#define CLEAR_ARRAY _IO (MD_MAJOR, 0x20)
#define ADD_NEW_DISK _IOW (MD_MAJOR, 0x21, mdu_disk_info_t)
#define HOT_REMOVE_DISK _IO (MD_MAJOR, 0x22)
#define SET_ARRAY_INFO _IOW (MD_MAJOR, 0x23, mdu_array_info_t)
#define SET_DISK_INFO _IO (MD_MAJOR, 0x24)
#define WRITE_RAID_INFO _IO (MD_MAJOR, 0x25)
#define UNPROTECT_ARRAY _IO (MD_MAJOR, 0x26)
#define PROTECT_ARRAY _IO (MD_MAJOR, 0x27)
#define HOT_ADD_DISK _IO (MD_MAJOR, 0x28)
#define SET_DISK_FAULTY _IO (MD_MAJOR, 0x29)
#define HOT_GENERATE_ERROR _IO (MD_MAJOR, 0x2a)
以上ioctl command配置multiple device设备。这些command主要就是管理diakarray,涉及配置diskarray的信息、add disk以及set disk fault等操作。
#define RUN_ARRAY _IOW (MD_MAJOR, 0x30, mdu_param_t)
#define START_ARRAY _IO (MD_MAJOR, 0x31)
#define STOP_ARRAY _IO (MD_MAJOR, 0x32)
#define STOP_ARRAY_RO _IO (MD_MAJOR, 0x33)
#define RESTART_ARRAY_RW _IO (MD_MAJOR, 0x34)
以上ioctl command主要控制diskarray的使用。涉及的操作主要有start, stop, restart等。
通过这些ioctl command可以在user space控制multiple device。如果参考mdadm的源码可以清楚地看出这些ioctl command是如何调用的。Mdadm的源码分析将在另外一篇文章中给出。
2.1 ioctl函数的实现
在md.c中,ioctl函数对参数作一些检查和设置相应的数据结构,然后调用不同的函数来完成这些系统调用。其具体定义如下:
static int md_ioctl(struct inode *inode, struct file *file,unsigned int cmd, unsigned long arg)
{
。。。。。。
/* Commands dealing with the RAID driver but not any particular array: */
switch (cmd)
{
case RAID_VERSION: err = get_version(argp); goto done;
case PRINT_RAID_DEBUG: err = 0; md_print_devices(); goto done;
#ifndef MODULE
case RAID_AUTORUN: err = 0; autostart_arrays(arg); goto done;
#endif
default:;
}
/* Commands creating/starting a new array:*/
if (cmd == START_ARRAY) {
/* START_ARRAY doesn't need to lock the array as autostart_array does the locking, and it could even be a different array */
。。。。。。
err = autostart_array(new_decode_dev(arg));
。。。。。。
}
err = mddev_lock(mddev);/*lock the mddev*/
switch (cmd)
{
case SET_ARRAY_INFO:
{
/*copy array info data from userspace*/
if (mddev->pers) {
err = update_array_info(mddev, &info);
/*if error, do some thing*/
}
err = set_array_info(mddev, &info);
。。。。。。
}
/* Commands querying/configuring an existing array:
if we are initialised yet, only ADD_NEW_DISK or STOP_ARRAY is allowed */
if (!mddev->raid_disks && cmd != ADD_NEW_DISK && cmd != STOP_ARRAY && cmd != RUN_ARRAY) {
err = -ENODEV; goto abort_unlock;
}
/* Commands even a read-only array can execute: */
switch (cmd)
{
case GET_ARRAY_INFO: err = get_array_info(mddev, argp); goto 。。。;
case GET_DISK_INFO: err = get_disk_info(mddev, argp);goto done_unlock;
case RESTART_ARRAY_RW:err = restart_array(mddev);goto done_unlock;
case STOP_ARRAY: err = do_md_stop (mddev, 0); goto done_unlock;
case STOP_ARRAY_RO: err = do_md_stop (mddev, 1); goto done_unlock;
/* We have a problem here : there is no easy way to give a CHS virtual geometry. We currently pretend that we have a 2 heads 4 sectors (with a BIG number of cylinders...). This drives dosfs just mad... ;-) */
case HDIO_GETGEO:
if (!loc) {
err = -EINVAL;
goto abort_unlock;
}
err = put_user (2, (char __user *) &loc->heads);
if (err)
goto abort_unlock;
err = put_user (4, (char __user *) &loc->sectors);
if (err)
goto abort_unlock;
err = put_user(get_capacity(mddev->gendisk)/8,
(short __user *) &loc->cylinders);
if (err)
goto abort_unlock;
err = put_user (get_start_sect(inode->i_bdev),
(long __user *) &loc->start);
goto done_unlock;
}
/* The remaining ioctls are changing the state of the superblock, so we do not allow read-only arrays here: */
switch (cmd)
{
case ADD_NEW_DISK:
{
/*copy mddev info from user space, then…*/
err = add_new_disk(mddev, &info); goto done_unlock;
}
case HOT_REMOVE_DISK:
err = hot_remove_disk(mddev, new_decode_dev(arg));goto done_unlock;
case HOT_ADD_DISK:
err = hot_add_disk(mddev, new_decode_dev(arg)); goto done_unlock;
case SET_DISK_FAULTY:
err = set_disk_faulty(mddev, new_decode_dev(arg)); goto done_unlock;
case RUN_ARRAY:
err = do_md_run (mddev); goto done_unlock;
default:
。。。。。。; goto abort_unlock;
}
。。。。。。
}
从以上ioctl函数的定义,可以找到各个ioctl command对应的处理函数。其中一些的具体实现,在下面给出解释。
2.2 START_ARRAY ioctl具体实现
由以上ioctl的实现函数中可以看出,START_ARRAY系统调用在md.c中是由autostart_array()函数实现的。
在autostart_array()中,首先,依据传进来的参数dev_t startdev,把md设备在内核中的表示结构(mdk_rdev_t类型)引入到内核空间,这部分由md_import_device()函数实现;接着,对这个md设备进行验证;验证通过把这个设备加入pending_raid_disks链表;然后,循环遍历和加载子设备;最后,调用autorun_devices()启动md设备。
使用autorun_devices()函数启动设备的过程如下:
遍历pending_raid_disks链表,拿出一个挂起的disk,找到与其有相同UUID的disk,把所有这些disk从挂起链表转移到“same_set”链表。在“same_set”链表中找出superblock最新的一个,然后检查其superblock。如果,一切正常,那么run这个设备。
3 对superblock 0.9/1.0的支持
在md.c中,通过以下的结构数组来实现对两种superblock的支持。其具体定义如下:
struct super_type super_types[] = {
[0] = {
.name = "0.90.0",
.owner = THIS_MODULE,
.load_super = super_90_load,
.validate_super = super_90_validate,
.sync_super = super_90_sync,
},
[1] = {
.name = "md-1",
.owner = THIS_MODULE,
.load_super = super_1_load,
.validate_super = super_1_validate,
.sync_super = super_1_sync,
},
};
其中,数组的两个元素分别存储对superblock 0.9.0/1.0的操作函数指针,这些函数在md.c中定义。通过这些函数,可以完成对superblock的操作。
在md_p.h中分别定义了与物理硬盘中存储的数据相对应的superblock结构,它们分别是mdp_superblock_s(表示与0.9v的superblock相对应的数据结构)和mdp_superblock_1(表示与1.0v的superblock相对应的数据结构)。这两个数据结构的具体定义,可以参见md_p.h中的定义和注释。
3.1 对superblock的操作
这些操作涉及到写入、更新、验证、删除等操作。在md.c中,通过
struct super_type {
char *name;
struct module *owner;
int (*load_super)(mdk_rdev_t *rdev, mdk_rdev_t *refdev, int minor_version);
int (*validate_super)(mddev_t *mddev, mdk_rdev_t *rdev);
void (*sync_super)(mddev_t *mddev, mdk_rdev_t *rdev);
};
来保存控制superblock的各种操作函数。之所以这样设计,是因为在Linux kernel 2.6.9的md driver中,可以对superblcok 0.90.0和1.0两种superblcok版本进行操作。这样,就可以使用一个接口,实现对两种版本superblcok的读写、更新控制。其中,具体的superblcok接口函数定义如下:
int load_super(mdk_rdev_t *dev, mdk_rdev_t *refdev, int minor_version)
/*loads and validates a superblock on dev. if refdev != NULL, compare superblocks on both devices
Return:
0 - dev has a superblock that is compatible with refdev
1 - dev has a superblock that is compatible and newer than refdev so dev should be used as the refdev in future
-EINVAL superblock incompatible or invalid
-othererror e.g. -EIO
*/
int validate_super(mddev_t *mddev, mdk_rdev_t *dev)
/*verify that dev is acceptable into mddev. The first time, mddev->raid_disks will be 0, and data fromdev should be merged in. Subsequent calls check that dev is new enough.
Return 0 or -EINVAL
*/
void sync_super(mddev_t *mddev, mdk_rdev_t *dev)
/*Update the superblock for rdev with data in mddev. This does not write to disc.*/
从super_types[]数组中可以看出在md.c中,所使用的几个函数。以下,对superblock 0.90.0的几个操作函数进行分析。
加载superblock 0.90.0所使用的函数为static int super_90_load(),其具体定义如下:
static int super_90_load(mdk_rdev_t *rdev, mdk_rdev_t *refdev, int minor_version)
{
……
/* calculate the position of the superblock, it's at the end of the disk.*/
sb_offset = calc_dev_sboffset(rdev->bdev);
rdev->sb_offset = sb_offset;
ret = read_disk_sb(rdev);/*read info. From harddisk.*/
……
sb = (mdp_super_t*)page_address(rdev->sb_page);
/*fill the info of rdev below*/
……
/*check sb*/
……
rdev->preferred_minor = sb->md_minor;
rdev->data_offset = 0;
if (sb->level == MULTIPATH)
rdev->desc_nr = -1;
else
rdev->desc_nr = sb->this_disk.number;
if (refdev == 0)
ret = 1;
else {……
}
rdev->size = calc_dev_size(rdev, sb->chunk_size);
abort:
return ret;
}
在读写数据的时候,使用了bio接口。具体的读写函数如下:
static int sync_page_io(struct block_device *bdev, sector_t sector, int size,
struct page *page, int rw)
{
struct bio bio;
struct bio_vec vec;
struct completion event;
……/*set bio field*/
submit_bio(rw, &bio);
……
}
其他对于superblock的操作,与以上操作类似,这里就不再说明了。
4 md driver中内核线程的使用
在md.c中,定义了如下包含md driver中的线程的相关数据的结构,其定义如下:
typedef struct mdk_thread_s {
void (*run) (mddev_t *mddev);/*pointer of thread method*/ mddev_t *mddev;/*md info that the thread deal with*/ wait_queue_head_t wqueue; unsigned long flags;
struct completion *event;
struct task_struct *tsk;
const char *name;
} mdk_thread_t;
在md.c中,使用mdk_thread_t *md_register_thread(void (*run) (mddev_t *), mddev_t *mddev, const char *name)函数注册一个内核线程;使用void md_unregister_thread(mdk_thread_t *thread)注销一个内核线程。在RAID需要resync, recovery, start, stop等的时候使用以上函数来操纵线程。