md 장치 생성하는 Create 함수
Create 함수의 인자
main에서 프로그램 인자를 모두 읽은 후에 Create함수를 호출해서 md 장치를 생성합니다. 다음은 Create함수가 호출되는 코드입니다.
rv = Create(ss, devlist->devname,
ident.name, ident.uuid_set ? ident.uuid : NULL,
devs_found-1, devlist->next,
&s, &c, data_offset);
다음은 Create 함수의 정의입니다.
int Create(struct supertype *st, char *mddev,
char *name, int *uuid,
int subdevs, struct mddev_dev *devlist,
struct shape *s,
struct context *c, unsigned long long data_offset)
{
각 인자가 어떤 값인지를 알아보기 위해 gdb로 다음과 같이 Create함수를 호출해봤습니다.
(gdb) b Create
Breakpoint 1 at 0x422755: file Create.c, line 69.
(gdb) r --create /dev/md0 --level 1 --raid-disks 2 /dev/loop0 /dev/loop1
Starting program: /home/gohkim/work/tools/mdadm-mainstream/mdadm --create /dev/md0 --level 1 --raid-disks 2 /dev/loop0 /dev/loop1
Breakpoint 1, Create (st=0x0, mddev=0x7fffffffe78a "/dev/md0", name=0x7fffffffe05c "", uuid=0x0, subdevs=2,
devlist=0x6ba040, s=0x7fffffffdf70, c=0x7fffffffdfb0, data_offset=1) at Create.c:69
69 {
(gdb) p *devlist
$1 = {devname = 0x7fffffffe7ac "/dev/loop0", disposition = 0, writemostly = 0 '\000', used = 0, data_offset = 0,
next = 0x6ba070}
(gdb) p *s
$2 = {raiddisks = 2, sparedisks = 0, journaldisks = 0, level = 1, layout = 65534, layout_str = 0x0, chunk = 0,
bitmap_chunk = 65534, bitmap_file = 0x0, assume_clean = 0, write_behind = 0, size = 0}
(gdb) p *c
$3 = {readonly = 0, runstop = 0, verbose = 0, brief = 0, force = 0, homehost = 0x7fffffffe2b0 "ws00837",
require_homehost = 1, prefer = 0x0, export = 0, test = 0, subarray = 0x0, update = 0x0, scan = 0, SparcAdjust = 0,
autof = 0, delay = 5, freeze_reshape = 0, backup_file = 0x0, invalid_backup = 0, action = 0x0, nodes = 0,
homecluster = 0x0}
결론적으로 Create함수의 인자는 다음과 같이 이해할 수 있습니다.
- st: md파일의 슈퍼블럭, 아직 생성되지 않았음
- mddev: 생성될 md 파일의 이름 /dev/md0
- name: 사용하지않음
- devlist: raid를 구성할 /dev/loop0, /dev/loop1장치들의 리스트
- s: raid 장치 구성에 필요한 옵션들
- raiddisks: --raid-disks 옵션에서 전달된 장치 갯수
- level: --level 옵션에서 전달된 RAID level
- c: 기타 옵션들
슈퍼블럭(메타데이타)
struct mdinfo info, *infos;
...
memset(&info, 0, sizeof(info));
Create함수의 시작부분은 전달된 옵션들에 문제가 없는지를 확인합니다. 서로 충돌되는 옵션이 있으면 함수가 종료됩니다. 그리고 다음 코드에서 하위 장치을 열수있는 권한이 있는지 블럭 장치가 맞는지 등을 확인합니다.
for (dv=devlist; dv && !have_container; dv=dv->next, dnum++) {
...
dfd = open(dname, O_RDONLY);
if (dfd < 0) {
pr_err("cannot open %s: %s\n",
dname, strerror(errno));
exit(2);
}
if (fstat(dfd, &stb) != 0 ||
(stb.st_mode & S_IFMT) != S_IFBLK) {
close(dfd);
pr_err("%s is not a block device\n",
dname);
exit(2);
}
close(dfd);
...
다음은 RAID 장치의 슈퍼블럭의 포맷을 결정합니다. 어떤 포맷들이 있는지등 세부 사항은 다음 링크를 참고하세요.
예를 들면 다음과 같이 mdadm 프로그램의 --metadata 옵션으로 지정할 수도 있습니다.
# mdadm --create /dev/md0 --metadata 1.0 <blah>
대표적으로 0.90과 1.0 두가지가 있는데, 우리는 위에서 따로 지정하지 않았으므로 아래의 루프를 돌면서 디폴트 포맷을 찾게됩니다.
if (st == NULL) {
/* Need to choose a default metadata, which is different
* depending on geometry of array.
*/
int i;
char *name = "default";
for(i=0; !st && superlist[i]; i++) {
st = superlist[i]->match_metadata_desc(name);
if (!st)
continue;
if (do_default_layout)
s->layout = default_layout(st, s->level, c->verbose);
switch (st->ss->validate_geometry(
st, s->level, s->layout, s->raiddisks,
&s->chunk, s->size*2,
dv->data_offset, dname,
&freesize, c->verbose > 0)) {
case -1: /* Not valid, message printed, and not
* worth checking any further */
exit(2);
break;
case 0: /* Geometry not valid */
free(st);
st = NULL;
s->chunk = do_default_chunk ? UnSet : s->chunk;
break;
case 1: /* All happy */
break;
}
}
이 루프에서 superlist라는 배열을 검색하는데 이 배열은 util.c 파일에 아래와같이 정의되어있습니다.
struct superswitch *superlist[] =
{
&super0, &super1,
&super_ddf, &super_imsm,
&mbr, &gpt,
NULL };
이 배열은 super0, super1등의 struct superwitch 타입 객체들의 배열인데 super1은 다음과 같이 super1.c 파일에 정의되어있습니다.
struct superswitch super1 = {
#ifndef MDASSEMBLE
.examine_super = examine_super1,
.brief_examine_super = brief_examine_super1,
.export_examine_super = export_examine_super1,
.detail_super = detail_super1,
.brief_detail_super = brief_detail_super1,
.export_detail_super = export_detail_super1,
.write_init_super = write_init_super1,
.validate_geometry = validate_geometry1,
.add_to_super = add_to_super1,
.examine_badblocks = examine_badblocks_super1,
.copy_metadata = copy_metadata1,
#endif
.match_home = match_home1,
.uuid_from_super = uuid_from_super1,
.getinfo_super = getinfo_super1,
.container_content = container_content1,
.update_super = update_super1,
.init_super = init_super1,
.store_super = store_super1,
.compare_super = compare_super1,
.load_super = load_super1,
.match_metadata_desc = match_metadata_desc1,
.avail_size = avail_size1,
.add_internal_bitmap = add_internal_bitmap1,
.locate_bitmap = locate_bitmap1,
.write_bitmap = write_bitmap1,
.free_super = free_super1,
#if __BYTE_ORDER == BIG_ENDIAN
.swapuuid = 0,
#else
.swapuuid = 1,
#endif
.name = "1.x",
};
제가 실험한 환경에서 선택된 메타데이터 포맷은 다음과 같이 1.0 버전입니다.
(gdb) p *st->ss
$25 = {examine_super = 0x4467eb <examine_super1>, brief_examine_super = 0x447626 <brief_examine_super1>,
brief_examine_subarrays = 0x0, export_examine_super = 0x447801 <export_examine_super1>,
examine_badblocks = 0x448336 <examine_badblocks_super1>, copy_metadata = 0x447ab8 <copy_metadata1>,
detail_super = 0x4480a8 <detail_super1>, brief_detail_super = 0x44820f <brief_detail_super1>,
export_detail_super = 0x4482b3 <export_detail_super1>, detail_platform = 0x0, export_detail_platform = 0x0,
uuid_from_super = 0x44863f <uuid_from_super1>, getinfo_super = 0x448693 <getinfo_super1>, getinfo_super_disks = 0x0,
match_home = 0x4485b9 <match_home1>, update_super = 0x448f34 <update_super1>, init_super = 0x44a060 <init_super1>,
add_to_super = 0x44a4bd <add_to_super1>, remove_from_super = 0x0, store_super = 0x44a716 <store_super1>,
write_init_super = 0x44abc6 <write_init_super1>, compare_super = 0x44b284 <compare_super1>,
load_super = 0x44b420 <load_super1>, load_container = 0x0, match_metadata_desc = 0x44bac8 <match_metadata_desc1>,
avail_size = 0x44bc49 <avail_size1>, min_acceptable_spare_size = 0x0,
add_internal_bitmap = 0x44bdaf <add_internal_bitmap1>, locate_bitmap = 0x44c27f <locate_bitmap1>,
write_bitmap = 0x44c34d <write_bitmap1>, free_super = 0x44c64f <free_super1>,
validate_geometry = 0x44c6d5 <validate_geometry1>, container_content = 0x448eea <container_content1>,
default_geometry = 0x0, kill_subarray = 0x0, update_subarray = 0x0, reshape_super = 0x0, manage_reshape = 0x0,
open_new = 0x0, set_array_state = 0x0, set_disk = 0x0, sync_metadata = 0x0, process_update = 0x0, prepare_update = 0x0,
activate_spare = 0x0, get_disk_controller_domain = 0x0, recover_backup = 0x0, validate_container = 0x0, swapuuid = 1,
external = 0, name = 0x48fc59 "1.x"}
이제 슈퍼블럭의 포맷을 결정했으니 /dev/loop0장치가 슈퍼블럭 포맷에 적합한지를 확인합니다.
skip_size_check:
if (c->runstop != 1 || c->verbose >= 0) {
int fd = open(dname, O_RDONLY);
if (fd <0 ) {
pr_err("Cannot open %s: %s\n",
dname, strerror(errno));
fail=1;
continue;
}
warn |= check_ext2(fd, dname);
warn |= check_reiser(fd, dname);
warn |= check_raid(fd, dname);
if (strcmp(st->ss->name, "1.x") == 0 &&
st->minor_version >= 1)
/* metadata at front */
warn |= check_partitions(fd, dname, 0, 0);
else if (s->level == 1 || s->level == LEVEL_CONTAINER
|| (s->level == 0 && s->raiddisks == 1))
/* partitions could be meaningful */
warn |= check_partitions(fd, dname, freesize*2, s->size*2);
else
/* partitions cannot be meaningful */
warn |= check_partitions(fd, dname, 0, 0);
if (strcmp(st->ss->name, "1.x") == 0 &&
st->minor_version >= 1 &&
did_default &&
s->level == 1 &&
(warn & 1024) == 0) {
warn |= 1024;
pr_err("Note: this array has metadata at the start and\n"
" may not be suitable as a boot device. If you plan to\n"
" store '/boot' on this device please ensure that\n"
" your boot-loader understands md/v1.x metadata, or use\n"
" --metadata=0.90\n");
}
close(fd);
/dev/loop0 장치를 열어서 이미 슈퍼블럭에 1.x 포맷의 메타데이터가 있는건 아닌지, 파티션이 있는 장치가 아닌지 등을 확인해서 RAID를 만들 수 있는지를 검사합니다. 이상이 없다면 /dev/loop0를 닫고, 루프를 돌아 다음 장치를 검사합니다. 다음 장치를 검사할 때는 이미 슈퍼블럭의 포맷이 결정되었으므로, 슈퍼블럭 포맷을 결정하는 코드는 건너뛰고 바로 장치를 검사합니다.
md 장치 파일 생성
이제는 /dev/md0 장치 파일을 만들때가 됐습니다. create_mddev 함수로 장치 파일을 만듭니다.
mdfd = create_mddev(mddev, name, c->autof, LOCAL, chosen_name);
create_mddev함수는 /dev/md0 이라는 이름이 적합한지 등을 확인한 후에 다음과 같이 임시로 사용할 장치 파일을 만들었다가 unlink함수로 링크를 제거합니다.
snprintf(devname, sizeof(devname), "/dev/.tmp.md.%d:%d:%d",
(int)getpid(), major, minor);
if (mknod(devname, S_IFBLK|0600, makedev(major, minor)) == 0) {
fd = open(devname, flags);
unlink(devname);
}
unlink로 링크를 제거한다는 것은 파일 자체를 지운다는게 아니라, 파일시스템에 파일을 없애긴하지만 프로세스가 가지고있는 파일에 대한 정보는 유지한다는 것입니다. 즉 open으로 얻은 fd 디스크립터는 유지하지만, /dev 디렉토리를 보면 그런 파일은 존재하지 않게됩니다.
실제로 gdb를 써서 /dev/ 디렉토리에 다음과 같은 파일이 만들어졌다가 사라진것을 확인할 수 있습니다.
$ ls /dev/.tmp*
/dev/.tmp.md.25065:9:0
$ ls /dev/.tmp*
ls: cannot access /dev/.tmp*: No such file or directory
이제 create_mddev함수를 이용해서 새로 생성할 장치의 fd를 생성했습니다. fd를 생성했다는 것은 바로 md 커널 모듈을 사용하게 됐다는 것이므로 의미가 있는 것입니다. mknod로 장치 파일을 생성할 때 장치의 Major번호와 Minor번호를 지정합니다. Major번호는 9로 고정된 것입니다. 왜냐면 우리는 md 모듈이 장치를 만들도록 하고싶기 때문입니다. 다음처럼 /proc/devices 파일을 출력해보면 Major번호에 따라 어떤 커널 모듈과 연결되는지를 알 수 있습니다.
$ cat /proc/devices
...
Block devices:
1 ramdisk
259 blkext
7 loop
8 sd
9 md
Minor 번호는 장치의 순서대로 지정되는 것입니다. 첫번째 장치는 0번, 두번째 장치는 1번이 되는 식입니다.
Create함수를 계속 보면 md_get_version함수가 나옵니다.
vers = md_get_version(mdfd);
이 함수의 코드를 보면 다음과 같이 ioctl을 호출하는데 인자가 RAID_VERSION입니다.
int md_get_version(int fd)
{
struct stat stb;
mdu_version_t vers;
if (fstat(fd, &stb)<0)
return -1;
if ((S_IFMT&stb.st_mode) != S_IFBLK)
return -1;
if (ioctl(fd, RAID_VERSION, &vers) == 0)
return (vers.major*10000) + (vers.minor*100) + vers.patchlevel;
if (errno == EACCES)
return -1;
if (major(stb.st_rdev) == MD_MAJOR)
return (3600);
return -1;
}
그리고 md 드라이버 파일을 보면 바로 RAID_VERSION 인자를 처리하는 코드가 있습니다. 다음은 linux 커널 코드중에서 drivers/md/md.c 파일에있는 md_ioctl함수의 코드 중 일부분입니다.
static int md_ioctl(struct block_device *bdev, fmode_t mode,
unsigned int cmd, unsigned long arg)
{
int err = 0;
void __user *argp = (void __user *)arg;
struct mddev *mddev = NULL;
int ro;
if (!md_ioctl_valid(cmd))
return -ENOTTY;
switch (cmd) {
case RAID_VERSION:
case GET_ARRAY_INFO:
case GET_DISK_INFO:
break;
default:
if (!capable(CAP_SYS_ADMIN))
return -EACCES;
}
/*
* Commands dealing with the RAID driver but not any
* particular array:
*/
switch (cmd) {
case RAID_VERSION:
err = get_version(argp);
goto out;
md_ioctl은 어플에서 ioctl 시스템콜을 호출했을 때 호출되서 현재 md 모듈이 가고있는 정보를 반환해줍니다. 시스템콜의 호출에 대한 내용은 다른 커널 책을 참고하세요.
이렇게 장치 파일을 만들었다는 것은 그때부터 커널 모듈을 사용하기 시작하게 됐다는 의미입니다.