Linux软件RAID系统初始化架构总览

主初始化 (md_run_setup)

功能:软件RAID驱动在系统启动时的总控初始化

执行流程

1. 创建设备文件 → 2. 检查自动检测配置 → 3. 执行自动检测 → 4. 设置驱动

关键特性

  • 设备接口准备:创建 /dev/md0 控制设备
  • 配置灵活性:支持 raid_noautodetect 内核参数

设备文件管理 (create_dev + find_in_devfs)

功能:在devfs环境中创建设备文件和符号链接

协作流程

create_dev:
1. 清理旧文件 → 2. devfs设备处理 → 3. 设备查找 → 4. 符号链接创建
    ↓
find_in_devfs (递归调用):
1. 目录读取 → 2. 深度优先搜索 → 3. 设备号匹配 → 4. 路径构建

设计特点

  • devfs兼容:充分支持设备文件系统
  • 递归搜索:深度优先遍历目录树
  • 相对路径:创建可移植的符号链接

驱动配置 (md_setup_drive)

功能:根据内核启动参数配置和启动RAID阵列

配置流程

for 每个配置条目:
1. 参数解析 → 2. 设备创建 → 3. 磁盘收集 → 4. 阵列配置 → 5. 阵列启动

阵列类型支持

  • 持久性阵列:从超级块读取配置
  • 非持久性阵列:运行时配置参数

数据结构关系体系

内核启动参数
md_setup_args 配置数组
md_setup_drive 处理
devices 设备号数组
MD设备创建
阵列运行
devfs文件系统
find_in_devfs 搜索
设备路径查找
create_dev 链接创建
自动检测系统
autostart_arrays
设备导入和分组
autorun_devices

软件RAID驱动在系统启动时的初始化md_run_setup

void __init md_run_setup(void)
{
	create_dev("/dev/md0", MKDEV(MD_MAJOR, 0), "md/0");
	if (raid_noautodetect)
		printk(KERN_INFO "md: Skipping autodetection of RAID arrays. (raid=noautodetect)\n");
	else {
		int fd = sys_open("/dev/md0", 0, 0);
		if (fd >= 0) {
			sys_ioctl(fd, RAID_AUTORUN, raid_autopart);
			sys_close(fd);
		}
	}
	md_setup_drive();
}
int __init create_dev(char *name, dev_t dev, char *devfs_name)
{
	char path[64];

	sys_unlink(name);
	if (devfs_name && devfs_name[0]) {
		if (strncmp(devfs_name, "/dev/", 5) == 0)
			devfs_name += 5;
		sprintf(path, "/dev/%s", devfs_name);
		if (sys_access(path, 0) == 0)
			return sys_symlink(devfs_name, name);
	}
	if (!dev)
		return -1;
	strcpy(path, "/dev");
	if (find_in_devfs(path, new_encode_dev(dev)) < 0)
		return -1;
	return sys_symlink(path + 5, name);
}

md_run_setup 函数

函数声明

void __init md_run_setup(void)
  • __init:这个函数只在系统初始化时使用,初始化完成后内存会被释放
  • void:没有返回值
  • 这是MD驱动在系统启动时的初始化函数

创建设备文件

	create_dev("/dev/md0", MKDEV(MD_MAJOR, 0), "md/0");
  • create_dev:创建设备文件的函数
  • "/dev/md0":要创建的设备文件路径
  • MKDEV(MD_MAJOR, 0):创建设备号,MD_MAJOR是MD设备的主设备号,0是次设备号
  • "md/0"devfs(设备文件系统)中的设备名

检查自动检测配置

	if (raid_noautodetect)
		printk(KERN_INFO "md: Skipping autodetection of RAID arrays. (raid=noautodetect)\n");
  • raid_noautodetect:内核启动参数,如果设置则跳过自动检测
  • 如果设置了该参数,打印跳过自动检测的信息
  • 这允许用户在启动时通过内核参数控制RAID自动检测行为

执行自动检测

	else {
		int fd = sys_open("/dev/md0", 0, 0);
		if (fd >= 0) {
			sys_ioctl(fd, RAID_AUTORUN, raid_autopart);
			sys_close(fd);
		}
	}
  • 如果没有禁用自动检测:
    • sys_open("/dev/md0", 0, 0):打开刚刚创建的md0设备文件
    • fd >= 0:检查文件打开是否成功
    • sys_ioctl(fd, RAID_AUTORUN, raid_autopart):执行自动检测ioctl命令
    • sys_close(fd):关闭设备文件

设置驱动器

	md_setup_drive();
}
  • 调用MD驱动设置函数
  • 完成MD系统的初始化

create_dev 函数

函数声明

int __init create_dev(char *name, dev_t dev, char *devfs_name)
  • __init:初始化时使用的函数
  • name:要创建的设备文件路径
  • dev:设备号
  • devfs_namedevfs中的设备名

变量声明和清理旧文件

{
	char path[64];

	sys_unlink(name);
  • path[64]:临时路径缓冲区
  • sys_unlink(name):删除已存在的设备文件(如果有)
  • 确保从头开始创建设备文件

devfs设备处理

	if (devfs_name && devfs_name[0]) {
		if (strncmp(devfs_name, "/dev/", 5) == 0)
			devfs_name += 5;
  • 检查devfs_name是否存在且非空
  • strncmp(devfs_name, "/dev/", 5):检查设备名是否以"/dev/"开头
  • 如果是,将指针向前移动5个字符,跳过"/dev/"前缀

检查devfs设备是否存在

		sprintf(path, "/dev/%s", devfs_name);
		if (sys_access(path, 0) == 0)
			return sys_symlink(devfs_name, name);
	}
  • sprintf(path, "/dev/%s", devfs_name):构造完整的devfs设备路径
  • sys_access(path, 0):检查该设备文件是否存在且可访问
  • 如果存在,创建符号链接并返回

无设备号的情况

	if (!dev)
		return -1;
  • 如果没有提供设备号,返回错误-1
  • 这是安全检查,确保有有效的设备号

devfs中查找设备

	strcpy(path, "/dev");
	if (find_in_devfs(path, new_encode_dev(dev)) < 0)
		return -1;
  • strcpy(path, "/dev"):初始化路径为/dev目录
  • find_in_devfs(path, new_encode_dev(dev)):在devfs中查找对应设备号的设备
  • new_encode_dev(dev):将设备号编码为devfs使用的格式
  • 如果查找失败,返回错误-1

创建符号链接

	return sys_symlink(path + 5, name);
}
  • path + 5:跳过"/dev/"前缀,只使用相对路径
  • sys_symlink(path + 5, name):创建从name指向实际设备的符号链接
  • 返回操作结果

函数功能详解

md_run_setup 功能

主要功能:Linux软件RAID驱动在系统启动时的初始化例程

详细执行流程

  1. 设备文件准备阶段

    • 创建主要的MD控制设备 /dev/md0
    • 为后续的ioctl操作提供接口
  2. 自动检测配置检查

    • 检查内核启动参数 raid_noautodetect
    • 允许用户在启动时控制RAID自动检测行为
    • 提供灵活的部署选项
  3. 自动检测执行阶段

    • 打开刚创建的MD设备文件
    • 通过ioctl调用触发RAID阵列的自动检测
    • 传递 raid_autopart 参数控制分区行为
  4. 驱动设置完成阶段

    • 调用MD驱动的最终设置函数
    • 完成整个MD子系统的初始化

create_dev 功能

主要功能:在devfs环境中创建设备文件符号链接

详细执行流程

  1. 清理准备阶段

    • 删除可能已存在的旧设备文件
    • 确保干净的创建环境
  2. devfs优先处理

    • 优先使用devfs设备名称
    • 处理devfs路径格式
    • 检查devfs设备是否实际存在
  3. 设备号验证

    • 确保提供了有效的设备号
    • 防止无效设备创建
  4. 设备查找阶段

    • devfs文件系统中查找对应设备号的设备节点
    • 使用设备号编码进行精确匹配
  5. 符号链接创建

    • 创建从用户指定名称到实际设备节点的符号链接
    • 处理路径格式以确保正确链接

devfs文件系统中递归查找指定设备号的设备文件find_in_devfs

static int __init find_in_devfs(char *path, unsigned dev)
{
	char *end = path + strlen(path);
	int rest = path + 64 - end;
	int size;
	char *p = read_dir(path, &size);
	char *s;

	if (!p)
		return -1;
	for (s = p; s < p + size; s += ((struct linux_dirent64 *)s)->d_reclen) {
		struct linux_dirent64 *d = (struct linux_dirent64 *)s;
		if (strlen(d->d_name) + 2 > rest)
			continue;
		switch (d->d_type) {
			case DT_BLK:
				sprintf(end, "/%s", d->d_name);
				if (bstat(path) != dev)
					break;
				kfree(p);
				return 0;
			case DT_DIR:
				if (strcmp(d->d_name, ".") == 0)
					break;
				if (strcmp(d->d_name, "..") == 0)
					break;
				sprintf(end, "/%s", d->d_name);
				if (find_in_devfs(path, dev) < 0)
					break;
				kfree(p);
				return 0;
		}
	}
	kfree(p);
	return -1;
}

函数声明和变量定义

static int __init find_in_devfs(char *path, unsigned dev)
{
	char *end = path + strlen(path);
	int rest = path + 64 - end;
	int size;
	char *p = read_dir(path, &size);
	char *s;
  • static int __init:静态初始化函数,只在系统启动时运行
  • char *path:要搜索的目录路径(输入输出参数)
  • unsigned dev:要查找的设备号
  • char *end = path + strlen(path):指向路径字符串的末尾
  • int rest = path + 64 - end:计算剩余缓冲区空间(路径最大64字节)
  • int size:目录内容大小
  • char *p = read_dir(path, &size):读取目录内容
  • char *s:目录项遍历指针

读取目录和初始检查

	if (!p)
		return -1;
  • if (!p):检查目录读取是否成功
  • 如果 read_dir 返回NULL,表示目录读取失败,返回-1

遍历目录项

	for (s = p; s < p + size; s += ((struct linux_dirent64 *)s)->d_reclen) {
		struct linux_dirent64 *d = (struct linux_dirent64 *)s;
		if (strlen(d->d_name) + 2 > rest)
			continue;
  • for (s = p; s < p + size; ...):遍历整个目录缓冲区
  • s += ((struct linux_dirent64 *)s)->d_reclen:移动到下一个目录项
  • struct linux_dirent64 *d = (struct linux_dirent64 *)s:转换为目录项结构
  • if (strlen(d->d_name) + 2 > rest):检查文件名长度是否超出缓冲区剩余空间
    • +2 是为了 / 和字符串结束符 \0
    • 如果超出,跳过这个目录项

处理块设备文件

		switch (d->d_type) {
			case DT_BLK:
				sprintf(end, "/%s", d->d_name);
				if (bstat(path) != dev)
					break;
				kfree(p);
				return 0;
  • case DT_BLK:处理块设备类型的目录项
  • sprintf(end, "/%s", d->d_name):将文件名追加到路径末尾
  • if (bstat(path) != dev):获取该路径的设备号并与目标比较
    • bstat(path):获取文件对应的设备号
    • 如果不匹配,跳出switch继续下一个
  • 如果匹配,释放目录缓冲区并返回0(成功)

处理子目录(递归搜索)

			case DT_DIR:
				if (strcmp(d->d_name, ".") == 0)
					break;
				if (strcmp(d->d_name, "..") == 0)
					break;
				sprintf(end, "/%s", d->d_name);
				if (find_in_devfs(path, dev) < 0)
					break;
				kfree(p);
				return 0;
  • case DT_DIR:处理目录类型的目录项
  • 跳过当前目录.和父目录..,避免无限递归
  • sprintf(end, "/%s", d->d_name):将子目录名追加到路径
  • if (find_in_devfs(path, dev) < 0)递归调用在子目录中继续搜索
  • 如果递归搜索成功,释放缓冲区并返回0

清理和返回

		}
	}
	kfree(p);
	return -1;
}
  • 循环结束:所有目录项都检查完毕
  • kfree(p):释放目录内容缓冲区
  • return -1:没有找到匹配的设备,返回失败

递归搜索过程示意图

开始搜索 /dev
遍历目录项
md DT_DIR
其他文件
递归搜索 /dev/md
遍历子目录项
0 DT_BLK
1 DT_BLK
其他文件
检查设备号匹配
匹配?
返回成功路径 /dev/md/0
继续搜索

函数功能详解

主要功能:在devfs文件系统中递归查找指定设备号的设备文件

详细执行流程

  1. 初始化阶段

    • 计算当前路径的末尾位置
    • 检查缓冲区剩余空间
    • 读取目录内容到内存
  2. 目录遍历阶段

    • 逐个处理目录项
    • 跳过文件名过长的项(防止缓冲区溢出)
    • 根据文件类型分别处理
  3. 块设备检查阶段

    • 对于每个块设备文件
    • 构造完整路径
    • 获取设备号并比较
    • 找到匹配立即返回
  4. 目录递归阶段

    • 对于每个子目录(跳过...
    • 递归调用搜索函数
    • 深度优先搜索整个目录树
  5. 清理返回阶段

    • 释放目录内容内存
    • 返回搜索结果

根据内核启动参数设置和启动软件RAID阵列md_setup_drive

static void __init md_setup_drive(void)
{
	int minor, i, ent, partitioned;
	dev_t dev;
	dev_t devices[MD_SB_DISKS+1];

	for (ent = 0; ent < md_setup_ents ; ent++) {
		int fd;
		int err = 0;
		char *devname;
		mdu_disk_info_t dinfo;
		char name[16], devfs_name[16];

		minor = md_setup_args[ent].minor;
		partitioned = md_setup_args[ent].partitioned;
		devname = md_setup_args[ent].device_names;

		sprintf(name, "/dev/md%s%d", partitioned?"_d":"", minor);
		sprintf(devfs_name, "/dev/md/%s%d", partitioned?"d":"", minor);
		if (partitioned)
			dev = MKDEV(mdp_major, minor << MdpMinorShift);
		else
			dev = MKDEV(MD_MAJOR, minor);
		create_dev(name, dev, devfs_name);
		for (i = 0; i < MD_SB_DISKS && devname != 0; i++) {
			char *p;
			char comp_name[64];
			u32 rdev;

			p = strchr(devname, ',');
			if (p)
				*p++ = 0;

			dev = name_to_dev_t(devname);
			if (strncmp(devname, "/dev/", 5) == 0)
				devname += 5;
			snprintf(comp_name, 63, "/dev/%s", devname);
			rdev = bstat(comp_name);
			if (rdev)
				dev = new_decode_dev(rdev);
			if (!dev) {
				printk(KERN_WARNING "md: Unknown device name: %s\n", devname);
				break;
			}

			devices[i] = dev;

			devname = p;
		}
		devices[i] = 0;

		if (!i)
			continue;

		printk(KERN_INFO "md: Loading md%s%d: %s\n",
			partitioned ? "_d" : "", minor,
			md_setup_args[ent].device_names);

		fd = sys_open(name, 0, 0);
		if (fd < 0) {
			printk(KERN_ERR "md: open failed - cannot start "
					"array %s\n", name);
			continue;
		}
		if (sys_ioctl(fd, SET_ARRAY_INFO, 0) == -EBUSY) {
			printk(KERN_WARNING
			       "md: Ignoring md=%d, already autodetected. (Use raid=noautodetect)\n",
			       minor);
			sys_close(fd);
			continue;
		}

		if (md_setup_args[ent].pers) {
			/* non-persistent */
			mdu_array_info_t ainfo;
			ainfo.level = pers_to_level(md_setup_args[ent].pers);
			ainfo.size = 0;
			ainfo.nr_disks =0;
			ainfo.raid_disks =0;
			while (devices[ainfo.raid_disks])
				ainfo.raid_disks++;
			ainfo.md_minor =minor;
			ainfo.not_persistent = 1;

			ainfo.state = (1 << MD_SB_CLEAN);
			ainfo.layout = 0;
			ainfo.chunk_size = md_setup_args[ent].chunk;
			err = sys_ioctl(fd, SET_ARRAY_INFO, (long)&ainfo);
			for (i = 0; !err && i <= MD_SB_DISKS; i++) {
				dev = devices[i];
				if (!dev)
					break;
				dinfo.number = i;
				dinfo.raid_disk = i;
				dinfo.state = (1<<MD_DISK_ACTIVE)|(1<<MD_DISK_SYNC);
				dinfo.major = MAJOR(dev);
				dinfo.minor = MINOR(dev);
				err = sys_ioctl(fd, ADD_NEW_DISK, (long)&dinfo);
			}
		} else {
			/* persistent */
			for (i = 0; i <= MD_SB_DISKS; i++) {
				dev = devices[i];
				if (!dev)
					break;
				dinfo.major = MAJOR(dev);
				dinfo.minor = MINOR(dev);
				sys_ioctl(fd, ADD_NEW_DISK, (long)&dinfo);
			}
		}
		if (!err)
			err = sys_ioctl(fd, RUN_ARRAY, 0);
		if (err)
			printk(KERN_WARNING "md: starting md%d failed\n", minor);
		sys_close(fd);
	}
}

函数声明和变量定义

static void __init md_setup_drive(void)
{
	int minor, i, ent, partitioned;
	dev_t dev;
	dev_t devices[MD_SB_DISKS+1];
  • static void __init:静态初始化函数,只在系统启动时运行
  • minor:MD设备的次设备号
  • i:循环计数器
  • ent:配置条目索引
  • partitioned:分区标志
  • dev:设备号变量
  • devices[MD_SB_DISKS+1]:设备号数组,存储组成RAID的所有磁盘设备

外层循环:处理每个配置条目

	for (ent = 0; ent < md_setup_ents ; ent++) {
		int fd;
		int err = 0;
		char *devname;
		mdu_disk_info_t dinfo;
		char name[16], devfs_name[16];
  • md_setup_ents:配置条目总数(来自内核启动参数)
  • fd:文件描述符
  • err:错误标志
  • devname:设备名字符串指针
  • dinfo:磁盘信息结构
  • name[16]:设备文件名缓冲区
  • devfs_name[16]devfs设备名缓冲区

获取配置参数

		minor = md_setup_args[ent].minor;
		partitioned = md_setup_args[ent].partitioned;
		devname = md_setup_args[ent].device_names;
  • 从配置数组中获取参数:
    • minor:次设备号
    • partitioned:是否为分区设备
    • devname:设备名称列表(逗号分隔)

创建设备文件

		sprintf(name, "/dev/md%s%d", partitioned?"_d":"", minor);
		sprintf(devfs_name, "/dev/md/%s%d", partitioned?"d":"", minor);
		if (partitioned)
			dev = MKDEV(mdp_major, minor << MdpMinorShift);
		else
			dev = MKDEV(MD_MAJOR, minor);
		create_dev(name, dev, devfs_name);
  • 根据分区标志生成不同的设备名:
    • 分区设备:/dev/md_d0devfs/dev/md/d0
    • 整盘设备:/dev/md0devfs/dev/md/0
  • 生成对应的设备号
  • 调用 create_dev 创建设备文件

解析设备名称列表

		for (i = 0; i < MD_SB_DISKS && devname != 0; i++) {
			char *p;
			char comp_name[64];
			u32 rdev;

			p = strchr(devname, ',');
			if (p)
				*p++ = 0;
  • 遍历设备名称列表(逗号分隔)
  • strchr(devname, ','):查找逗号分隔符
  • 如果找到逗号,将其替换为\0并移动指针到下一个设备名

转换设备名称为设备号

			dev = name_to_dev_t(devname);
			if (strncmp(devname, "/dev/", 5) == 0)
				devname += 5;
			snprintf(comp_name, 63, "/dev/%s", devname);
			rdev = bstat(comp_name);
			if (rdev)
				dev = new_decode_dev(rdev);
  • name_to_dev_t(devname):将设备名转换为设备号
  • 如果设备名以/dev/开头,跳过此前缀
  • 构造完整设备路径
  • bstat(comp_name):获取设备的实际设备号
  • 如果获取成功,使用实际的设备号

设备号验证和存储

			if (!dev) {
				printk(KERN_WARNING "md: Unknown device name: %s\n", devname);
				break;
			}

			devices[i] = dev;

			devname = p;
		}
		devices[i] = 0;
  • 检查设备号是否有效
  • 如果无效,打印警告并跳出循环
  • 将设备号存入数组
  • 移动到下一个设备名
  • 在数组末尾添加0作为结束标记

跳过无效配置

		if (!i)
			continue;
  • 如果没有有效的设备,跳过这个配置条目

打印加载信息

		printk(KERN_INFO "md: Loading md%s%d: %s\n",
			partitioned ? "_d" : "", minor,
			md_setup_args[ent].device_names);
  • 打印正在加载的MD设备信息

打开MD设备

		fd = sys_open(name, 0, 0);
		if (fd < 0) {
			printk(KERN_ERR "md: open failed - cannot start "
					"array %s\n", name);
			continue;
		}
  • 打开MD设备文件
  • 如果打开失败,打印错误并继续下一个配置

检查设备是否已自动检测

		if (sys_ioctl(fd, SET_ARRAY_INFO, 0) == -EBUSY) {
			printk(KERN_WARNING
			       "md: Ignoring md=%d, already autodetected. (Use raid=noautodetect)\n",
			       minor);
			sys_close(fd);
			continue;
		}
  • 尝试设置阵列信息
  • 如果返回-EBUSY,说明设备已被自动检测到
  • 打印警告信息,关闭设备并继续下一个

非持久性阵列设置

		if (md_setup_args[ent].pers) {
			/* non-persistent */
			mdu_array_info_t ainfo;
			ainfo.level = pers_to_level(md_setup_args[ent].pers);
			ainfo.size = 0;
			ainfo.nr_disks =0;
			ainfo.raid_disks =0;
			while (devices[ainfo.raid_disks])
				ainfo.raid_disks++;
			ainfo.md_minor =minor;
			ainfo.not_persistent = 1;

			ainfo.state = (1 << MD_SB_CLEAN);
			ainfo.layout = 0;
			ainfo.chunk_size = md_setup_args[ent].chunk;
			err = sys_ioctl(fd, SET_ARRAY_INFO, (long)&ainfo);
  • 为非持久性阵列设置参数:
    • level:RAID级别
    • size:大小为0(自动计算)
    • raid_disks:统计实际磁盘数量
    • not_persistent:标记为非持久性
    • state:设置为clean状态
    • chunk_size:块大小
  • 调用SET_ARRAY_INFO ioctl设置阵列信息

添加磁盘到非持久性阵列

			for (i = 0; !err && i <= MD_SB_DISKS; i++) {
				dev = devices[i];
				if (!dev)
					break;
				dinfo.number = i;
				dinfo.raid_disk = i;
				dinfo.state = (1<<MD_DISK_ACTIVE)|(1<<MD_DISK_SYNC);
				dinfo.major = MAJOR(dev);
				dinfo.minor = MINOR(dev);
				err = sys_ioctl(fd, ADD_NEW_DISK, (long)&dinfo);
			}
  • 遍历所有设备,逐个添加到阵列
  • 设置磁盘状态为活跃和同步
  • 调用ADD_NEW_DISK ioctl添加磁盘

持久性阵列设置

		} else {
			/* persistent */
			for (i = 0; i <= MD_SB_DISKS; i++) {
				dev = devices[i];
				if (!dev)
					break;
				dinfo.major = MAJOR(dev);
				dinfo.minor = MINOR(dev);
				sys_ioctl(fd, ADD_NEW_DISK, (long)&dinfo);
			}
		}
  • 对于持久性阵列,直接添加所有磁盘
  • 不需要设置阵列信息(从超级块读取)

启动阵列和清理

		if (!err)
			err = sys_ioctl(fd, RUN_ARRAY, 0);
		if (err)
			printk(KERN_WARNING "md: starting md%d failed\n", minor);
		sys_close(fd);
	}
}
  • 如果没有错误,启动阵列
  • 如果启动失败,打印警告信息
  • 关闭设备文件
  • 循环结束

函数功能详解

主要功能:根据内核启动参数设置和启动软件RAID阵列

详细执行流程

  1. 配置解析阶段

    • 遍历所有通过内核参数配置的RAID阵列
    • 解析每个配置条目的参数
  2. 设备准备阶段

    • 创建设备文件(如/dev/md0
    • 解析组件磁盘的设备名称列表
    • 将设备名转换为设备号
  3. 阵列配置阶段

    • 区分持久性和非持久性阵列
    • 设置RAID级别、磁盘数量等参数
    • 添加所有组件磁盘到阵列
  4. 阵列启动阶段

    • 启动配置好的RAID阵列
    • 处理启动失败的情况
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐