ioctl-RAID_VERSION

mdadm 툴은 md 디스크를 만들때 ioctl 함수를 이용해 RAID_VERSION 명령을 전달해서 md 모듈의 버전 정보를 얻습니다.

다음 코드는 mdadm 툴에서 ioctl을 호출하는 코드입니다.

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;

ioctl에 RAID_VERSION 명령을 전달하고 커널에게 mdu_version_t 객체의 포인터를 넘겨서 커널이 버전정보를 vers 변수에 저장하도록 합니다.

다음은 md 모듈에서 ioctl 시스템콜을 처리하는 md_ioctl 함수입니다.

static int md_ioctl(struct block_device *bdev, fmode_t mode,
            unsigned int cmd, unsigned long arg)
{
......
    void __user *argp = (void __user *)arg;
......
    switch (cmd) {
    case RAID_VERSION:
        err = get_version(argp);
        goto out;

md_ioctl 함수의 cmd 인자가 바로 RAID_VERSION 값입니다. 그리고 arg 인자가 mdu_version_t 객체의 포인터입니다.

다음은 get_version 함수의 코드입니다.

static int get_version(void __user *arg)
{
    mdu_version_t ver;

    ver.major = MD_MAJOR_VERSION;
    ver.minor = MD_MINOR_VERSION;
    ver.patchlevel = MD_PATCHLEVEL_VERSION;

    if (copy_to_user(arg, &ver, sizeof(ver)))
        return -EFAULT;

    return 0;
}

MD_MAJOR_VERSION, MD_MINOR_VERSION, MD_PATCHLEVEL_VERSION 값을 mdu_version_t 객체에 저장한 후 사용자 영역의 버퍼로 복사합니다. 그러면 ioctl을 호출한 사용자는 커널이 저장한 값들을 읽을 수 있습니다.

debug with gdb

gdb로 커널이 실행되는 것을 직접 보고 싶다면 다음처럼 gdb에서 ctrl-c를 누릅니다. 그러면 gdb의 프롬프트가 출력되는데 "b md_ioctl" 명령으로 breakpoint를 만들어줍니다.

(gdb) c
Continuing.
^C
Program received signal SIGINT, Interrupt.
native_safe_halt () at ./arch/x86/include/asm/irqflags.h:50
50    }
(gdb) b md_ioctl
Breakpoint 3 at 0xffffffff81619a20: file drivers/md/md.c, line 6691.

그리고 c 명령을 내리면 커널이 계속 동작합니다. 이때 커널이 실행되고 있는 터미널에서 mdadm 툴을 이용해서 md 디스크크를 만듭니다.

/ # mdadm --create /dev/md0 --level 1 --raid-devices 2 /dev/vda /dev/vdb
[66833.732136] md_probe start
[66833.732379] CPU: 0 PID: 1026 Comm: mdadm Not tainted 4.4.0+ #82
[66833.732834] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS Ubuntu-1.8.2-1ubuntu1 04/01/2014
[66833.733558]  00000000000001ff ffff88000756fb38 ffffffff8130c0df 0000000000900000
[66833.734126]  ffff88000756fb50 ffffffff816152d8 0000000000900000 ffff88000756fbb0
[66833.734681]  ffffffff814e0d2e 0000000000000009 ffffffff816152c0 0000000000000000
[66833.735214] Call Trace:
[66833.735430]  [<ffffffff8130c0df>] dump_stack+0x44/0x55
[66833.735814]  [<ffffffff816152d8>] md_probe+0x18/0x40
[66833.736184]  [<ffffffff814e0d2e>] kobj_lookup+0xfe/0x160
[66833.736559]  [<ffffffff816152c0>] ? md_alloc+0x340/0x340
[66833.736963]  [<ffffffff812fd8cd>] get_gendisk+0x2d/0x100
[66833.737333]  [<ffffffff811a1df0>] __blkdev_get+0x120/0x3b0
[66833.737731]  [<ffffffff811a221a>] blkdev_get+0x19a/0x310
[66833.738146]  [<ffffffff812b8cb8>] ? selinux_file_open+0x98/0xd0
[66833.738556]  [<ffffffff811a2436>] blkdev_open+0x56/0x70
[66833.738945]  [<ffffffff8116ad6a>] do_dentry_open+0x1fa/0x2f0
[66833.739367]  [<ffffffff811a23e0>] ? blkdev_get_by_dev+0x50/0x50
[66833.739802]  [<ffffffff8116c011>] vfs_open+0x51/0x60
[66833.740154]  [<ffffffff8117a34f>] path_openat+0x57f/0x1270
[66833.740539]  [<ffffffff811195de>] ? unlock_page+0x5e/0x60
[66833.740942]  [<ffffffff8117c029>] do_filp_open+0x79/0xd0
[66833.741314]  [<ffffffff811645bf>] ? kmem_cache_alloc+0x2f/0x130
[66833.741751]  [<ffffffff8117b201>] ? getname_flags+0x51/0x1f0
[66833.742110]  [<ffffffff8118825a>] ? __alloc_fd+0x3a/0x170
[66833.742505]  [<ffffffff8116c376>] do_sys_open+0x126/0x200
[66833.743052]  [<ffffffff8116c469>] SyS_open+0x19/0x20
[66833.743396]  [<ffffffff8188f86e>] entry_SYSCALL_64_fastpath+0x12/0x71
[66833.744747] md_probe end

그러면 다음과같이 커널에서 md_ioctl가 실행될 때 gdb가 커널 실행을 멈추고 사용자 명령을 기다립니다.

(gdb) c
Continuing.
[Switching to Thread 4]

Breakpoint 3, md_ioctl (bdev=0xffff880006174000, mode=393247, cmd=2148272400, arg=140722013883360)
    at drivers/md/md.c:6691
6691    {
(gdb) p/x cmd
$10 = 0x800c0910

이 다음부터는 gdb로 사용자 어플리케이션을 디버깅하는 것과 같습니다. 커널을 한줄씩 실행할 수도 있고 원하는 변수들의 값을 확인할 수도 있습니다.

results matching ""

    No results matching ""