md_init

md_init은 md 모듈의 시작 지점입니다. md 모듈을 커널에 정적으로 포함되도록 빌드했으므로, 커널이 부팅되면서 md_init 함수가 호출됩니다.

md_init은 다음 3가지를 실행합니다.

  • workqueue생성
    • md, md_misc라는 이름의 workqueue를 생성합니다.
  • md_probe 등록
    • md 장치의 주번호는 9입니다.
    • 주번호가 9인 장치파일이 생성될때마다 md_probe함수가 호출됩니다.
  • /proc/mdstat 파일 생성
    • 모든 md 디스크의 상태를 출력하는 /proc/mdstat 파일을 생성합니다.

md workqueue

다음과 같이 "md"라는 이름의 workqueue를 생성합니다.

static struct workqueue_struct *md_wq;
......
    md_wq = alloc_workqueue("md", WQ_MEM_RECLAIM, 0);
    if (!md_wq)
        goto err_wq;

예를 들어 md_flush_request라는 함수는 다음과 같이 md_wq에 mddev->flush_work를 추가합니다.

void md_flush_request(struct mddev *mddev, struct bio *bio)
{
    spin_lock_irq(&mddev->lock);
    wait_event_lock_irq(mddev->sb_wait,
                !mddev->flush_bio,
                mddev->lock);
    mddev->flush_bio = bio;
    spin_unlock_irq(&mddev->lock);

    INIT_WORK(&mddev->flush_work, submit_flushes);
    queue_work(md_wq, &mddev->flush_work);
}
EXPORT_SYMBOL(md_flush_request);

INIT_WORK 매크로를 통해 mddev->flush_work에 submit_flushes 함수를 연결하고 workqueue에 등록합니다. 그러면 잠시 시간이 흐른 후에 submit__flushes 함수가 호출됩니다.

submit_flushes 함수의 역할은 나중에 분석하겠습니다.

md_probe

blk_register_region함수는 커널에 블럭 장치를 등록하는 함수입니다. 이 블럭 장치는 주번호가 9 (=MD_MAJOR)이고 부번호는 0~511 을 가질 수 있습니다. 그리고 이런 장치 번호에 해당하는 장치가 생성되면 md_probe 함수를 호출합니다.

blk_register_region(MKDEV(MD_MAJOR, 0), 512, THIS_MODULE,
                md_probe, NULL, NULL);

blk_register_region 함수는 kobj_map 함수를 호출해서 등록된 장치를 위한 struct probe 객체를 생성하고 커널에 등록됩니다.
다음 코드가 바로 struct probe 객체를 생성하고 커널에 등록하는 코드입니다.

int kobj_map(struct kobj_map *domain, dev_t dev, unsigned long range,
         struct module *module, kobj_probe_t *probe,
         int (*lock)(dev_t, void *), void *data)
{
    unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1;
    unsigned index = MAJOR(dev);
    unsigned i;
    struct probe *p;

    if (n > 255)
        n = 255;

    p = kmalloc_array(n, sizeof(struct probe), GFP_KERNEL);
    if (p == NULL)
        return -ENOMEM;

    for (i = 0; i < n; i++, p++) {
        p->owner = module;
        p->get = probe;
        p->lock = lock;
        p->dev = dev;
        p->range = range;
        p->data = data;
    }
    mutex_lock(domain->lock);
    for (i = 0, p -= n; i < n; i++, p++, index++) {
        struct probe **s = &domain->probes[index % 255];
        while (*s && (*s)->range < range)
            s = &(*s)->next;
        p->next = *s;
        *s = p;
    }
...

kobj_map은 등록된 장치에 필요한 갯수만큼 probe 객체를 생성하고 domain->probes 리스트에 추가합니다.

md_probe 함수가 정확히 언제 호출되는지 알아보기 위해 다음과 같이 dump_stack 함수를 md_probe 함수안에 추가하고 커널을 부팅해보겠습니다.

diff --git a/drivers/md/md.c b/drivers/md/md.c
index 61aacab..4ec2ace 100644
--- a/drivers/md/md.c
+++ b/drivers/md/md.c
@@ -5072,7 +5072,12 @@ static int md_alloc(dev_t dev, char *name)

 static struct kobject *md_probe(dev_t dev, int *part, void *data)
 {
+
+       pr_err("md_probe start\n");
+       dump_stack();
+       
        md_alloc(dev, NULL);
+       pr_err("md_probe end\n");
        return NULL;
 }

커널의 부팅 과정에서 md 장치를 만들지 않기 때문에 커널의 부팅 메세지에 md_probe가 없습니다. 그러므로 다음과 같이 md0 장치를 만들어보면 그때야 md_probe가 호출되는 것을 알 수 있습니다.

/ # mdadm --create /dev/md0 -l 1 -n 2 /dev/vda /dev/vdb
mdadm: /dev/vda appears to be part of a raid array:
       level=raid1 devices=2 ctime=Mon Dec 19 16:42:00 2016
mdadm: Note: this array has metadata at the start and
    may not be suitable as a boot device.  If you plan to
    store '/boot' on this device please ensure that
    your boot-loader understands md/v1.x metadata, or use
    --metadata=0.90
mdadm: /dev/vdb appears to be part of a raid array:
       level=raid1 devices=2 ctime=Mon Dec 19 16:42:00 2016
Continue creating array? y
[   48.189329] md_probe start
[   48.189550] CPU: 2 PID: 1005 Comm: mdadm Not tainted 4.4.0+ #82
[   48.190012] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS Ubuntu-1.8.2-1ubuntu1 04/01/2014
[   48.190744]  00000000000001ff ffff880006503b88 ffffffff8130c0df 0000000000900000
[   48.191303]  ffff880006503ba0 ffffffff816152d8 0000000000900000 ffff880006503c00
[   48.191876]  ffffffff814e0d2e 0000000000000009 ffffffff816152c0 0000000000000000
[   48.192405] Call Trace:
[   48.192580]  [<ffffffff8130c0df>] dump_stack+0x44/0x55
[   48.193063]  [<ffffffff816152d8>] md_probe+0x18/0x40
[   48.193423]  [<ffffffff814e0d2e>] kobj_lookup+0xfe/0x160
[   48.193795]  [<ffffffff816152c0>] ? md_alloc+0x340/0x340
[   48.194163]  [<ffffffff812fd8cd>] get_gendisk+0x2d/0x100
[   48.194527]  [<ffffffff811a20d5>] blkdev_get+0x55/0x310
[   48.194890]  [<ffffffff8108d45d>] ? wake_up_bit+0x1d/0x20
[   48.195263]  [<ffffffff811a175c>] ? bdget+0x10c/0x120
[   48.195611]  [<ffffffff811a2436>] blkdev_open+0x56/0x70
[   48.195975]  [<ffffffff8116ad6a>] do_dentry_open+0x1fa/0x2f0
[   48.196361]  [<ffffffff811a23e0>] ? blkdev_get_by_dev+0x50/0x50
[   48.196769]  [<ffffffff8116c011>] vfs_open+0x51/0x60
[   48.197112]  [<ffffffff8117a34f>] path_openat+0x57f/0x1270
[   48.197537]  [<ffffffff811646bb>] ? kmem_cache_alloc+0x12b/0x130
[   48.197951]  [<ffffffff812b6875>] ? selinux_inode_alloc_security+0x35/0x90
[   48.198418]  [<ffffffff8117c029>] do_filp_open+0x79/0xd0
[   48.198862]  [<ffffffff812ae9cd>] ? security_d_instantiate+0x2d/0x50
[   48.199300]  [<ffffffff811645bf>] ? kmem_cache_alloc+0x2f/0x130
[   48.199657]  [<ffffffff8117b201>] ? getname_flags+0x51/0x1f0
[   48.200153]  [<ffffffff8118825a>] ? __alloc_fd+0x3a/0x170
[   48.200564]  [<ffffffff8116c376>] do_sys_open+0x126/0x200
[   48.200944]  [<ffffffff8116c469>] SyS_open+0x19/0x20
[   48.201291]  [<ffffffff8188f86e>] entry_SYSCALL_64_fastpath+0x12/0x71
[   48.202087] md_probe end
[   48.202331] random: mdadm urandom read with 73 bits of entropy available
mdadm: Defaulting to version 1.2 metadata
[   48.223550] md: bind<vda>
[   48.224128] md: bind<vdb>
[   48.224809] md/raid1:md0: not clean -- starting background reconstruction
[   48.225335] md/raid1:md0: active with 2 out of 2 mirrors
[   48.225787] md0: detected capacity change from 0 to 104726528
mdadm: array /de[   48.226312] md: resync of RAID array md0
v/md0 started.
[   48.226340] mdadm (1005) used greatest stack depth: 13408 bytes left
[   48.226938] md: minimum _guaranteed_  speed: 1000 KB/sec/disk.
[   48.227240] md: using maximum available idle IO bandwidth (but not more than 200000 KB/sec) for resync.
[   48.227734] md: using 128k window, over a total of 102272k.
/ # [   52.210333] md: md0: resync done.

콜스택을 보면 장치 파일을 open했을 때 kobj_lookup 함수에서 md_probe를 호출합니다.
우리가 이미 분석했듯이 mdadm 툴에서 md 장치를 만들 때 mknod 시스템콜을 써서 장치 파일을 만들고 open시스템콜을 호출합니다. 바로 그때 md_probe가 호출됩니다.

다음은 kobj_lookup 코드에서 md_probe를 호출하는 코드입니다.

struct kobject *kobj_lookup(struct kobj_map *domain, dev_t dev, int *index)
{
    struct kobject *kobj;
    struct probe *p;
    unsigned long best = ~0UL;

retry:
    mutex_lock(domain->lock);
    for (p = domain->probes[MAJOR(dev) % 255]; p; p = p->next) {
        struct kobject *(*probe)(dev_t, int *, void *);
        struct module *owner;
        void *data;

        if (p->dev > dev || p->dev + p->range - 1 < dev)
            continue;
        if (p->range - 1 >= best)
            break;
        if (!try_module_get(p->owner))
            continue;
        owner = p->owner;
        data = p->data;
        probe = p->get;
        best = p->range - 1;
        *index = dev - p->dev;
        if (p->lock && p->lock(dev, data) < 0) {
            module_put(owner);
            continue;
        }
        mutex_unlock(domain->lock);
        kobj = probe(dev, index, data);

kobj_lookup함수를 보면 커널에 등록된 장치들의 장치번호를 확인해서 현재 생성되는 장치의 장치 번호에 해당하는 probe 객체를 찾아서 p->get에 저장된 함수포인터를 호출합니다.

/proc/mdstat

다음과 같이 proc_create 함수로 /proc/mdstat 파일을 만듭니다.

proc_create("mdstat", S_IRUGO, NULL, &md_seq_fops);

mdstat파일을 읽을 때 호출되는 함수가 md_seq_fops에 정의되어있는데, 커널에 등록된 모든 md 장치들이 all_mddevs 리스트에 연결되어있으므로, 이 리스트에서 각 장치들을 찾아서 정보를 확인하게 됩니다.
코드만 봐도 이해할 수 있는 내용이므로 여기에서 더이상 자세히 설명하지는 않겠습니다.

results matching ""

    No results matching ""