Linux 内核中RAID5源码详解之RAID模块声明
系统整体布局
在讲RAID5之前,我们先来思考下这个问题:我们平时写的C函数,比如说write()
是怎么将数据写到计算机的硬盘上的?整个系统指令执行的流程是什么?我们带着这个问题来了解系统的整体布局。
这是系统的整体布局的示意图,比如说问题中的write()
执行流程则为用户态调用该函数,然后进行系统调用,将指令和参数传给虚拟文件系统层,然后再具体的文件系统上执行,文件系统再将指令和参数转换为写请求(包含地址和数据)传给通用设备层,最后通过设备驱动将数据写到物理硬盘上。可是我们会想了,这个问题和我们要讲的RAID5有啥关系呢?别急嘛,好酒要一点点品,同样了解了整个系统的布局再具体到某一点,应该会理解的更好一点哦~
MD及RAID模块
接下来就来看看MD和RAID模块在上述的布局中起到什么作用。首先如果将MD和RAID模块加入到上图中的话,那加到什么位置呢?下图告诉你答案。
从图中可看出,文件系统将请求(写的地址和数据)发送给下层的MD模块,而MD模块则选用一种阵列结构来执行这条指令,于是指令被传送到了RAID上,由于RAID有好几种等级,RAID0,RAID1,RAID5,RAID6,RAID10等等,经过RAID模块时,会将请求中的地址转换为某块盘上的偏移量,同时根据写的数据以及原来的数据计算新的parity(校验位),再将数据和parity写到物理硬盘上。所以MD和RAID模块起到一个承上启下的作用,但是MD和RAID之间有什么联系呢?
其实RAID是MD上的一个子模块,上层将请求传到MD,MD再讲请求发送到具体的子模块上,再通过这些子模块执行具体的请求。也可以看成MD实现了下层设备阵列的抽象。
模块的声明
由于内核将MD和RAID模块化,可是怎么通过代码的形式来识别这些模块呢?首先我们看看RAID5模块的声明(MD模块的声明和这个类似,我们最关注的是RAID模块),代码在raid5.c中(其实包含的是raid4、raid5、raid6的源代码)。
static int __init raid5_init(void)
{
raid5_wq = alloc_workqueue("raid5wq",
WQ_UNBOUND|WQ_MEM_RECLAIM|WQ_CPU_INTENSIVE|WQ_SYSFS, 0);
if (!raid5_wq)
return -ENOMEM;
register_md_personality(&raid6_personality);
register_md_personality(&raid5_personality);
register_md_personality(&raid4_personality);
return 0;
}
static void raid5_exit(void)
{
unregister_md_personality(&raid6_personality);
unregister_md_personality(&raid5_personality);
unregister_md_personality(&raid4_personality);
destroy_workqueue(raid5_wq);
}
module_init(raid5_init);
module_exit(raid5_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("RAID4/5/6 (striping with parity) personality for MD");
MODULE_ALIAS("md-personality-4"); /* RAID5 */
MODULE_ALIAS("md-raid5");
MODULE_ALIAS("md-raid4");
MODULE_ALIAS("md-level-5");
MODULE_ALIAS("md-level-4");
MODULE_ALIAS("md-personality-8"); /* RAID6 */
MODULE_ALIAS("md-raid6");
MODULE_ALIAS("md-level-6");
/* This used to be two separate modules, they were: */
MODULE_ALIAS("raid5");
MODULE_ALIAS("raid6");
结合下面语句的解释看,结果一目了然啦。
Module_init(raid5_init);//模块的初始化
Module_exit(raid5_exit);//模块的反初始化
MODULE_LICENSE(“GPL”);//模块的证书版本
MODULE_DESCRIPTION(XXX);//模块的描述,human-readable
MODULE_ALIAS(xx);//模块的别名
RAID5模块的初始化是由raid5_init()
函数来完成的,追踪此函数发现这条语句register_md_personality(&raid5_personality);
,这条的意思是将raid5_personality注册到md_personality中。nice,现在就来看看raid5_personality是什么:
static struct md_personality raid5_personality =
{
.name = "raid5",
.level = 5,
.owner = THIS_MODULE,
.make_request = make_request,
.run = run,
.free = raid5_free,
.status = status,
.error_handler = error,
.hot_add_disk = raid5_add_disk,
.hot_remove_disk= raid5_remove_disk,
.spare_active = raid5_spare_active,
.sync_request = sync_request,
.resize = raid5_resize,
.size = raid5_size,
.check_reshape = raid5_check_reshape,
.start_reshape = raid5_start_reshape,
.finish_reshape = raid5_finish_reshape,
.quiesce = raid5_quiesce,
.takeover = raid5_takeover,
.congested = raid5_congested,
.mergeable_bvec = raid5_mergeable_bvec,
};
也许开始看这个时发现不是很懂,那好我们再把md_personality贴出来,看看有什么联系:
struct md_personality
{
char *name;
int level;
struct list_head list;
struct module *owner;
void (*make_request)(struct mddev *mddev, struct bio *bio);
int (*run)(struct mddev *mddev);
void (*free)(struct mddev *mddev, void *priv);
void (*status)(struct seq_file *seq, struct mddev *mddev);
/* error_handler must set ->faulty and clear ->in_sync
* if appropriate, and should abort recovery if needed
*/
void (*error_handler)(struct mddev *mddev, struct md_rdev *rdev);
int (*hot_add_disk) (struct mddev *mddev, struct md_rdev *rdev);
int (*hot_remove_disk) (struct mddev *mddev, struct md_rdev *rdev);
int (*spare_active) (struct mddev *mddev);
sector_t (*sync_request)(struct mddev *mddev, sector_t sector_nr, int *skipped, int go_faster);
int (*resize) (struct mddev *mddev, sector_t sectors);
sector_t (*size) (struct mddev *mddev, sector_t sectors, int raid_disks);
int (*check_reshape) (struct mddev *mddev);
int (*start_reshape) (struct mddev *mddev);
void (*finish_reshape) (struct mddev *mddev);
/* quiesce moves between quiescence states
* 0 - fully active
* 1 - no new requests allowed
* others - reserved
*/
void (*quiesce) (struct mddev *mddev, int state);
/* takeover is used to transition an array from one
* personality to another. The new personality must be able
* to handle the data in the current layout.
* e.g. 2drive raid1 -> 2drive raid5
* ndrive raid5 -> degraded n+1drive raid6 with special layout
* If the takeover succeeds, a new 'private' structure is returned.
* This needs to be installed and then ->run used to activate the
* array.
*/
void *(*takeover) (struct mddev *mddev);
/* congested implements bdi.congested_fn().
* Will not be called while array is 'suspended' */
int (*congested)(struct mddev *mddev, int bits);
/* mergeable_bvec is use to implement ->merge_bvec_fn */
int (*mergeable_bvec)(struct mddev *mddev,
struct bvec_merge_data *bvm,
struct bio_vec *biovec);
};
这下清楚了吧,哦,原来这是一个个指针函数,可是这两个是怎么联系到一起的呢?我们再回头看register_md_personality(&raid5_personality)
:
int register_md_personality(struct md_personality *p)
{
printk(KERN_INFO "md: %s personality registered for level %d\n",
p->name, p->level);
spin_lock(&pers_lock);//加锁
list_add_tail(&p->list, &pers_list);//添加到pers_list中
spin_unlock(&pers_lock);//解锁
return 0;
}
这个函数最重要的一行就是讲传进来的md_personality结构添加到pers_list中,pers_list维护了整个MD的md_personality结构。可是如何通过MD调用到RAID5呢?细心了解源码,在md_run()
中给出了这种调用关系,由于函数过长,我们只贴出所需要的地方,首先获取对应RAID级别的md_personality结构:pers = find_pers(mddev->level, mddev->clevel);
,追踪这个函数:
static struct md_personality *find_pers(int level, char *clevel)
{
struct md_personality *pers;
list_for_each_entry(pers, &pers_list, list) {//查找相应的pers
if (level != LEVEL_NONE && pers->level == level)
return pers;
if (strcmp(pers->name, clevel)==0)
return pers;
}
return NULL;
}
仔细看注释的那句,居然有pers_list,好眼熟,这不就是register时那个全局list嘛!!!哎呀,好巧好巧,所以这个函数就是在pers_list中一个个比,如果level相同或者name相同,则找到了对应的md_personality结构,返回。
查看md.c中的代码,会发现有很多地方出现了如下形式的语句:md->pers->make_request
,这就是通过模块的声明,将RAID模块声明到MD模块中,通过md_personality的结构调用指针函数,从而实现RAID的功能,嘻嘻,回过头来看看也不是很复杂耶~
至此,RAID模块的声明已经完成,嘻嘻,不难吧,同样,对于模块的删除,在raid5_exit()
中:
static void raid5_exit(void)
{
unregister_md_personality(&raid6_personality);
unregister_md_personality(&raid5_personality);
unregister_md_personality(&raid4_personality);
destroy_workqueue(raid5_wq);
}
追踪unregister_md_personality()
:
int unregister_md_personality(struct md_personality *p)
{
printk(KERN_INFO "md: %s personality unregistered\n", p->name);
spin_lock(&pers_lock);
list_del_init(&p->list);
spin_unlock(&pers_lock);
return 0;
}
和register相反,只是从链表中删除而已,道理一样。
关于模块的声明和删除已经讲得差不多了,下篇主要介绍下内核中RAID5的stripe_head管理和守护线程raid5d,这是了解RAID5运行原理的基石哦。