后浪笔记一零二四

1. raid的概念

RAID: Redundant Array of indepensive Disk,独立磁盘冗余阵列

磁盘阵列是把多个磁盘组成一个阵列,当作单一磁盘使用,它将数据以分段(striping)的方式储存在不同的磁盘中,存取数据时,阵列中的相关磁盘一起操作。

磁盘阵列利用的不同的技术,称为RAID level,不同的level针对不同的系统及应用,以解决数据安全的问题。

可提供的功能:

  1. 冗余(容错)
  2. 性能提升

RAID分类:

  1. 硬件RAID:用RAID接口卡来实现,需要内核支持其驱动,并且该类设备显示为SCSI设备,代号为/dev/sd*
  2. 软件RAID:用内核中的MD(multiple devices)模块实现,该类设备表示为/etc/md*;在linux系统中使用mdadm工具管理软RAID

几种常见RAID:

2. mdadm操作教程

3. md源码分析

md代码在linux内核树中十多年经久不衰,跟作者neil brown,一位久经考验的开源战士,是分不开的。neil brown的博客地址是 http://blog.neil.brown.name ,和md相关的问题基本上都可以在这个博客上找到答案。

内核代码一般都是从kconfig和Makefile开始看,下面是drivers/md/Kconfig的代码:

# SPDX-License-Identifier: GPL-2.0-only
#
# Block device driver configuration
#

menuconfig MD
	bool "Multiple devices driver support (RAID and LVM)"
	depends on BLOCK
	help
	  Support multiple physical spindles through a single logical device.
	  Required for RAID and logical volume management.

if MD

config BLK_DEV_MD
	tristate "RAID support"

#...省略若干

endif # MD

在内核源代码目录下敲下 make menuconfig 命令后,就会出现一个文本配置界面,在文本配置界面中可以选择需要编译进内核的模块。

drivers/md/Kconfig文件中,包含有 menuconfig MD,所以文本配置界面中会有MD的那一项。

当选中MD之后,文本配置界面就会出现config BLK_DEV_MD及其之后的选项,这些选项一般都有两种状态,一个是tristate,表示内建、模块、移除三种状态,另一个是bool,表示选中或不选中。

depends on表示正向依赖,如果选上了这个模块,那么正向依赖的模块也会自动选上,正向依赖模块所依赖的模块也会选上。

知道了Kconfig的基本配置,就可以按需定制内核,把不需要的统统去掉。

drivers/md/Makefile文件中:

# SPDX-License-Identifier: GPL-2.0
#
# Makefile for the kernel software RAID and LVM drivers.
#

dm-mod-y	+= dm.o dm-table.o dm-target.o dm-linear.o dm-stripe.o \
		   dm-ioctl.o dm-io.o dm-kcopyd.o dm-sysfs.o dm-stats.o \
		   dm-rq.o dm-io-rewind.o
dm-multipath-y	+= dm-path-selector.o dm-mpath.o
dm-historical-service-time-y += dm-ps-historical-service-time.o
dm-io-affinity-y += dm-ps-io-affinity.o
dm-queue-length-y += dm-ps-queue-length.o
dm-round-robin-y += dm-ps-round-robin.o
dm-service-time-y += dm-ps-service-time.o
dm-snapshot-y	+= dm-snap.o dm-exception-store.o dm-snap-transient.o \
		    dm-snap-persistent.o
dm-mirror-y	+= dm-raid1.o
dm-log-userspace-y += dm-log-userspace-base.o dm-log-userspace-transfer.o
dm-bio-prison-y += dm-bio-prison-v1.o dm-bio-prison-v2.o
dm-thin-pool-y	+= dm-thin.o dm-thin-metadata.o
dm-cache-y	+= dm-cache-target.o dm-cache-metadata.o dm-cache-policy.o \
		    dm-cache-background-tracker.o
dm-cache-smq-y	+= dm-cache-policy-smq.o
dm-ebs-y	+= dm-ebs-target.o
dm-era-y	+= dm-era-target.o
dm-clone-y	+= dm-clone-target.o dm-clone-metadata.o
dm-verity-y	+= dm-verity-target.o
dm-zoned-y	+= dm-zoned-target.o dm-zoned-metadata.o dm-zoned-reclaim.o

md-mod-y	+= md.o md-bitmap.o
raid456-y	+= raid5.o raid5-cache.o raid5-ppl.o

# Note: link order is important.  All raid personalities
# and must come before md.o, as they each initialise
# themselves, and md.o may use the personalities when it
# auto-initialised.

obj-$(CONFIG_MD_RAID0)		+= raid0.o
obj-$(CONFIG_MD_RAID1)		+= raid1.o
obj-$(CONFIG_MD_RAID10)		+= raid10.o
obj-$(CONFIG_MD_RAID456)	+= raid456.o
obj-$(CONFIG_MD_CLUSTER)	+= md-cluster.o
obj-$(CONFIG_BCACHE)		+= bcache/
obj-$(CONFIG_BLK_DEV_MD)	+= md-mod.o

如果在Kconfig中选中了config BLK_DEV_MD,那么Makefile就会编译生成md-mod.ko模块,那这个模块由哪几个文件生成的呢?

md-mod-y += md.o md-bitmap.o这行可以看出,md-mod.ko模块是由md.c和md-bitmap.c这两个文件编译来的。

那么md-mod.ko模块是从哪个文件开始的呢?这就要找module_init函数,这个函数在md.c中定义的:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
// #ifndef 通常和 #define 一起使用,以防止头文件中的内容被多次包含(即多重包含),这种技术被称为“头文件保护”或“防重复包含”。
// 这是因为在大型项目中,多个源文件可能会包含相同的头文件,而这些头文件又可能互相包含其他头文件。
// 如果不加以控制,这会导致编译器遇到重复定义的问题,进而引发编译错误。
// #ifndef MY_HEADER_H
// #define MY_HEADER_H
// #endif // MY_HEADER_H

#ifndef MODULE
static void autorun_array(struct mddev *mddev)
{
	struct md_rdev *rdev;
	int err;

	if (list_empty(&mddev->disks))
		return;

	pr_info("md: running: ");

	rdev_for_each(rdev, mddev) {
		pr_cont("<%pg>", rdev->bdev);
	}
	pr_cont("\n");

	err = do_md_run(mddev);
	if (err) {
		pr_warn("md: do_md_run() returned %d\n", err);
		do_md_stop(mddev, 0);
	}
}

/*
 * lets try to run arrays based on all disks that have arrived
 * until now. (those are in pending_raid_disks)
 *
 * the method: pick the first pending disk, collect all disks with
 * the same UUID, remove all from the pending list and put them into
 * the 'same_array' list. Then order this list based on superblock
 * update time (freshest comes first), kick out 'old' disks and
 * compare superblocks. If everything's fine then run it.
 *
 * If "unit" is allocated, then bump its reference count
 */
static void autorun_devices(int part)
{
	struct md_rdev *rdev0, *rdev, *tmp;
	struct mddev *mddev;

	pr_info("md: autorun ...\n");
	while (!list_empty(&pending_raid_disks)) {
		int unit;
		dev_t dev;
		LIST_HEAD(candidates);
		rdev0 = list_entry(pending_raid_disks.next,
					 struct md_rdev, same_set);

		pr_debug("md: considering %pg ...\n", rdev0->bdev);
		INIT_LIST_HEAD(&candidates);
		rdev_for_each_list(rdev, tmp, &pending_raid_disks)
			if (super_90_load(rdev, rdev0, 0) >= 0) {
				pr_debug("md:  adding %pg ...\n",
					 rdev->bdev);
				list_move(&rdev->same_set, &candidates);
			}
		/*
		 * now we have a set of devices, with all of them having
		 * mostly sane superblocks. It's time to allocate the
		 * mddev.
		 */
		// 变量part表示磁盘的第几个分区,那么就知道mdp_major中字母p是表示part的意思,
		// 而mdp_major就表示用磁盘分区创建的阵列。
		if (part) {
			dev = MKDEV(mdp_major,
				    rdev0->preferred_minor << MdpMinorShift);
			unit = MINOR(dev) >> MdpMinorShift;
		} else {
			dev = MKDEV(MD_MAJOR, rdev0->preferred_minor);
			unit = MINOR(dev);
		}
		if (rdev0->preferred_minor != unit) {
			pr_warn("md: unit number in %pg is bad: %d\n",
				rdev0->bdev, rdev0->preferred_minor);
			break;
		}

		mddev = md_alloc(dev, NULL);
		if (IS_ERR(mddev))
			break;

		if (mddev_suspend_and_lock(mddev))
			pr_warn("md: %s locked, cannot run\n", mdname(mddev));
		else if (mddev->raid_disks || mddev->major_version
			 || !list_empty(&mddev->disks)) {
			pr_warn("md: %s already running, cannot run %pg\n",
				mdname(mddev), rdev0->bdev);
			mddev_unlock_and_resume(mddev);
		} else {
			pr_debug("md: created %s\n", mdname(mddev));
			mddev->persistent = 1;
			rdev_for_each_list(rdev, tmp, &candidates) {
				list_del_init(&rdev->same_set);
				if (bind_rdev_to_array(rdev, mddev))
					export_rdev(rdev, mddev);
			}
			autorun_array(mddev);
			mddev_unlock_and_resume(mddev);
		}
		/* on success, candidates will be empty, on error
		 * it won't...
		 */
		rdev_for_each_list(rdev, tmp, &candidates) {
			list_del_init(&rdev->same_set);
			export_rdev(rdev, mddev);
		}
		mddev_put(mddev);
	}
	pr_info("md: ... autorun DONE.\n");
}
#endif /* !MODULE */

#...省略若干

static int __init md_init(void)
{
	int ret = -ENOMEM;

	// 创建 md_wq 工作队列: 用于flush命令
	md_wq = alloc_workqueue("md", WQ_MEM_RECLAIM, 0);
	if (!md_wq)
		goto err_wq;

	// 创建 md_misc_wq 工作队列: misc是miscellaneous的简写,是杂项的意思,用于处理一些零零碎碎的事情
	md_misc_wq = alloc_workqueue("md_misc", 0, 0);
	if (!md_misc_wq)
		goto err_misc_wq;

	md_bitmap_wq = alloc_workqueue("md_bitmap", WQ_MEM_RECLAIM | WQ_UNBOUND,
				       0);
	if (!md_bitmap_wq)
		goto err_bitmap_wq;

	// 创建了 md 块设备
	ret = __register_blkdev(MD_MAJOR, "md", md_probe);
	if (ret < 0)
		goto err_md;

	// 创建了 mdp 块设备
	ret = __register_blkdev(0, "mdp", md_probe);
	if (ret < 0)
		goto err_mdp;
	mdp_major = ret;

	// 注册关机回调函数,主要作用是停止阵列线程,刷数据操作。
	register_reboot_notifier(&md_notifier);
	// 注册sysctl函数,用于控制阵列最小和最大的sync速度。
	raid_table_header = register_sysctl("dev/raid", raid_table);

	// 注册proc函数,于是有了目录/proc/mdstat,该目录的显示由函数md_seq_show控制
	md_geninit();
	return 0;

err_mdp:
	unregister_blkdev(MD_MAJOR, "md");
err_md:
	destroy_workqueue(md_bitmap_wq);
err_bitmap_wq:
	destroy_workqueue(md_misc_wq);
err_misc_wq:
	destroy_workqueue(md_wq);
err_wq:
	return ret;
}

本文发表于 0001-01-01,最后修改于 0001-01-01。

本站永久域名「 jiavvc.top 」,也可搜索「 后浪笔记一零二四 」找到我。


上一篇 « 下一篇 »

赞赏支持

请我吃鸡腿 =^_^=

i ysf

云闪付

i wechat

微信

推荐阅读

Big Image