Multiple device driver 源文件不完全注释

本文深入探讨Linux 2.6.9内核中的MD驱动,涉及模块加载、卸载过程,包括md_init和md_exit函数的详细解析。此外,文章分析了ioctl系统调用的实现,如START_ARRAY等,以及对superblock 0.9/1.0的支持。同时,讨论了MD驱动中内核线程的使用,用于resync、recovery等操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这篇是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等的时候使用以上函数来操纵线程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值