디바이스 드라이버를 등록하는 과정을 알아보기 위해서는 커널 초기화 과정을 따라갈 필요가 있다.

 

사용하는 CPU가 arm인 경우 초기화를 담당하는 어셈블 코드에서 C 코드로의 이동은

 

/kernel/arch/arm/kernel/head-common.S 파일의 __map_switched 에서 start_kernel()로 분기하면서 리눅스 커널의 초기화 과정이 시작된다.

__mmap_switched:
        adr     r3, __switch_data + 4

        ldmia   r3!, {r4, r5, r6, r7}
        cmp     r4, r5                          @ Copy data segment if needed
1:      cmpne   r5, r6
        ldrne   fp, [r4], #4
        strne   fp, [r5], #4
        bne     1b

        mov     fp, #0                          @ Clear BSS (and zero fp)
1:      cmp     r6, r7
        strcc   fp, [r6],#4
        bcc     1b

        ldmia   r3, {r4, r5, r6, r7, sp}
        str     r9, [r4]                        @ Save processor ID
        str     r1, [r5]                        @ Save machine type
        str     r2, [r6]                        @ Save atags pointer
        bic     r4, r0, #CR_A                   @ Clear 'A' bit
        stmia   r7, {r0, r4}                    @ Save control register values
        b       start_kernel

 

start_kernel()은 /kernel/init/main.c 에서 선언하고 있으며 주로 Hardware와 밀접한 내용들을 초기화 하는 과정을 포함하며 리눅스 커널과 관련된 대부분의 초기화 과정이 여기에서 이루어 진다.

 

start_kernel()의 제일 마지막은 rest_init()을 호출한다.

 

asmlinkage void __init start_kernel(void)
{
        char * command_line;
        extern struct kernel_param __start___param[], __stop___param[];

 

        ......

 

 

        acpi_early_init(); /* before LAPIC and SMP init */

 

        /* Do the rest non-__init'ed, we're now alive */
        rest_init();
}

 

리눅스 커널이 동작하면 /init 프로세스와 kthreadd 프로세스가 제일 처음 실행되게 되는데 이를 시작하는 것이 rest_init()이다.

 

static void noinline __init_refok rest_init(void)
        __releases(kernel_lock)
{
        int pid;

        kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
        numa_default_policy();
        pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
        kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
        unlock_kernel();

        /*
         * The boot idle thread must execute schedule()
         * at least once to get things moving:
         */
        init_idle_bootup_task(current);
        preempt_enable_no_resched();
        schedule();
        preempt_disable();

        /* Call into cpu_idle with preempt disabled */
        cpu_idle();
}

 

 

rest_init()에서 구동되는 kernel_init() 에서는 부팅 과정에서 커널이 생성하는 첫번째 프로세스인 /init을 생성하기 직전에 do_basic_setup()을 호출하여 정적인 디바이스 드라이버 파일을 초기화(등록) 하게 된다.

 

static int __init kernel_init(void * unused)
{
        lock_kernel();
        /*
         * init can run on any cpu.
         */
        set_cpus_allowed_ptr(current, CPU_MASK_ALL_PTR);
        /*
         * Tell the world that we're going to be the grim
         * reaper of innocent orphaned children.
         *
         * We don't want people to have to make incorrect
         * assumptions about where in the task array this
         * can be found.
         */
        init_pid_ns.child_reaper = current;

 

        cad_pid = task_pid(current);

 

        smp_prepare_cpus(setup_max_cpus); 

 

        do_pre_smp_initcalls();

 

        smp_init();
        sched_init_smp();

 

        cpuset_init_smp();

 

        do_basic_setup();

 

        /*
         * check if there is an early userspace init.  If yes, let it do all
         * the work
         */

        if (!ramdisk_execute_command)
                ramdisk_execute_command = "/init";

 

        if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
                ramdisk_execute_command = NULL;
                prepare_namespace();
        }

 

        /*
         * Ok, we have completed the initial bootup, and
         * we're essentially up and running. Get rid of the
         * initmem segments and start the user-mode stuff..
         */
        init_post();
        return 0;

 

do_basic_setup()에서 workqueue, driver, irq_proc 등을 호출하고 마지막으로 do_initcalls()를 호출한다. (do_pre_smp_initcalls() 함수고 do_basic_setup() 과 유사한 일을 수행한다)

 

static void __init do_basic_setup(void)
{
        rcu_init_sched(); /* needed by module_init stage. */
        /* drivers will send hotplug events */
        init_workqueues();
        usermodehelper_init();
        driver_init();
        init_irq_proc();
        do_initcalls();
}

 

do_initcalls()은 for 문을 이용해서 do_one_initcall()을 호출하게 된다.

 

extern initcall_t __initcall_start[], __initcall_end[], __early_initcall_end[];

static void __init do_initcalls(void)
{
        initcall_t *call;

        for (call = __early_initcall_end; call < __initcall_end; call++)
                do_one_initcall(*call);

        /* Make sure there is no pending stuff from the initcall sequence */
        flush_scheduled_work();
}

 

static void __init do_pre_smp_initcalls(void)
{
        initcall_t *call;

        for (call = __initcall_start; call < __early_initcall_end; call++)
                do_one_initcall(*call);

 

int do_one_initcall(initcall_t fn) 

{
        int count = preempt_count();
        ktime_t t0, t1, delta;
        char msgbuf[64];
        int result;

        if (initcall_debug) {
                printk("calling  %pF\n", fn);
                t0 = ktime_get();
        }

        result = fn(); 

 

        ...... 

 

여기서 보면 __early_initcall_end에서 __initcall_end 사이에 등록된 함수들을 호출하는 것을 알 수 있다.

이들은 보통 CPU에 따라서 vmlinux.lds.S 파일에 정의되어 있다. arm의 경우를 보면 /kernel/arch/arm/kernel/vmlinux.lds.S 파일에 정의되어 있다.

 

       .init : {                       /* Init code and data           */
                        INIT_TEXT
                _einittext = .;
                __proc_info_begin = .;
                        *(.proc.info.init)
                __proc_info_end = .;
                __arch_info_begin = .;
                        *(.arch.info.init)
                __arch_info_end = .;
                __tagtable_begin = .;
                        *(.taglist.init)
                __tagtable_end = .;
                . = ALIGN(16);
                __setup_start = .;
                        *(.init.setup)
                __setup_end = .;
                __early_begin = .;
                        *(.early_param.init)
                __early_end = .;
                __initcall_start = .;
                        INITCALLS
                __initcall_end = .;

 

                ..... 

        } 

 

INITCALLS는 /kernel/include/asm-generic/vmlinux.lds.h 에 선언되어 있는데 이를 이용하여 /kernel/arch/arm/kernel/vmlinux.lds.S 의 내용을 다시 정리하면 다음과 같다.

 

__initcall_start = .;
        *(.initcallearly.init)                                          \
        VMLINUX_SYMBOL(__early_initcall_end) = .;                       \
        *(.initcall0.init)                                              \
        *(.initcall0s.init)                                             \
        *(.initcall1.init)                                              \
        *(.initcall1s.init)                                             \
        *(.initcall2.init)                                              \
        *(.initcall2s.init)                                             \
        *(.initcall3.init)                                              \
        *(.initcall3s.init)                                             \
        *(.initcall4.init)                                              \
        *(.initcall4s.init)                                             \
        *(.initcall5.init)                                              \
        *(.initcall5s.init)                                             \
        *(.initcallrootfs.init)                                         \
        *(.initcall6.init)                                              \
        *(.initcall6s.init)                                             \
        *(.initcall7.init)                                              \
        *(.initcall7s.init)

__initcall_end = .; 

 

이 의미는 특정한 서브 섹션에 함수 포인터를 등록하기 위한 코드이다. 실제로 /kernel/include/linux/init.h 파일을 보면 이를 위한 매크로가 선언되어 있다.

 

#define __define_initcall(level,fn,id) \
        static initcall_t __initcall_##fn##id __used \
        __attribute__((__section__(".initcall" level ".init"))) = fn

/*
 * Early initcalls run before initializing SMP.
 *
 * Only for built-in code, not modules.
 */
#define early_initcall(fn)              __define_initcall("early",fn,early)

/*
 * A "pure" initcall has no dependencies on anything else, and purely
 * initializes variables that couldn't be statically initialized.
 *
 * This only exists for built-in code, not for modules.
 */
#define pure_initcall(fn)               __define_initcall("0",fn,0)

#define core_initcall(fn)               __define_initcall("1",fn,1)
#define core_initcall_sync(fn)          __define_initcall("1s",fn,1s)
#define postcore_initcall(fn)           __define_initcall("2",fn,2)
#define postcore_initcall_sync(fn)      __define_initcall("2s",fn,2s)
#define arch_initcall(fn)               __define_initcall("3",fn,3)
#define arch_initcall_sync(fn)          __define_initcall("3s",fn,3s)
#define subsys_initcall(fn)             __define_initcall("4",fn,4)
#define subsys_initcall_sync(fn)        __define_initcall("4s",fn,4s)
#define fs_initcall(fn)                 __define_initcall("5",fn,5)
#define fs_initcall_sync(fn)            __define_initcall("5s",fn,5s)
#define rootfs_initcall(fn)             __define_initcall("rootfs",fn,rootfs)
#define device_initcall(fn)             __define_initcall("6",fn,6)
#define device_initcall_sync(fn)        __define_initcall("6s",fn,6s)
#define late_initcall(fn)               __define_initcall("7",fn,7)
#define late_initcall_sync(fn)          __define_initcall("7s",fn,7s)

 

예를 들어 내가 Board 동작에 필요한 기본적인 드라이버들을 하나의 파일에 정의해서 일괄적으로 platform에 등록하는 함수를 만들어 두었다면 커널 초기화에 사용하기 위해서 마지막에 .initcall 섹센에 등록해야 한다.

 

내가 사용하는 TCC92xx 보드의 device.c 파일에 위와 관련된 코드(/kernel/arch/arm/mach-tcc92x/devices.c)가 있다.

 

static int __init tcc9200_init_devices(void)
{
        platform_add_devices(devices, ARRAY_SIZE(devices));


    return 0;
}

 

arch_initcall(tcc9200_init_devices);

 

위와 같이 arch_initcall 매크로를 이용해서 tcc9200_init_devices를 .initcall3.init 섹션에 tcc9200_init_device 함수 포인터를 등록하게 된다. 이는 위에서 설명한 것처럼 do_initcalls() 에서 섹션에 등록된 순서대로 호출되게 된다.

 

다른 예로 보면 arm의 경우 /kernel/arch/arm/kernel/time.c 파일에서 LED 제어와 sysfs를 위한 device를 등록한다.

 

static int __init leds_init(void)
{
        int ret;
        ret = sysdev_class_register(&leds_sysclass);
        if (ret == 0)
                ret = sysdev_register(&leds_device);
        if (ret == 0)
                ret = sysdev_create_file(&leds_device, &attr_event);
        return ret;
}

device_initcall(leds_init);

 

 static int __init timer_init_sysfs(void)
{
        int ret = sysdev_class_register(&timer_sysclass);
        if (ret == 0) {
                system_timer->dev.cls = &timer_sysclass;
                ret = sysdev_register(&system_timer->dev);
        }

        return ret;
}

device_initcall(timer_init_sysfs);

 

 

일반적인 디바이스 드라이버의 경우도 각각의 디바이스 드라이버 파일을 마지막에 보면 module_init()과 module_exit() 매크로로 커널에 모듈을 적재하고 제거할 때 호출하는 함수를 지정하는 매크로가 있다. 보통 디바이스 드라이버는 정적으로도 커널에 링크시킬 수 있고 모듈 형태로 사용할 수도 있는데 module_init 매크로에 대한 정의를 /kernel/include/linux/init.h 에서 보면 다음과 같이 두가지로 정의한다.

 

MODULE로 정의되지 않은 경우

 

#define device_initcall(fn)             __define_initcall("6",fn,6) 

 

......

 

#define __initcall(fn) device_initcall(fn)

  

...... 

 

/**
 * module_init() - driver initialization entry point
 * @x: function to be run at kernel boot time or module insertion
 *
 * module_init() will either be called during do_initcalls() (if
 * builtin) or at module insertion time (if a module).  There can only
 * be one per module.
 */
#define module_init(x)  __initcall(x);

 

즉 moudle_init 매크로는 디바이스 드라이버를 모듈로 컴파일 하지 않으면 device_initcall에 의해서  .initcall7.init 섹션에 등록되어 커널 초기화 과정에서 등록하게 된다.

 

MODULE로 정의되어 있는 경우는 다음과 같다.

 

/* Each module must use one module_init(). */
#define module_init(initfn)                                     \
        static inline initcall_t __inittest(void)               \
        { return initfn; }                                      \

        int init_module(void) __attribute__((alias(#initfn)));

/* This is only required if you want to be unloadable. */
#define module_exit(exitfn)                                     \
        static inline exitcall_t __exittest(void)               \
        { return exitfn; }                                      \
        void cleanup_module(void) __attribute__((alias(#exitfn)));

 

위의 경우는 __inittest()라는 초기화 함수 포인터를 넘겨주는 static 함수를 만들고 이를 모듈 드라이버 로딩 과정에서 호출할 수 있게 해준다.


출 처 : http://blog.daum.net/baramjin/16010994

댓글을 달아 주세요

 «이전 1 ··· 30 31 32 33 34 35 36 37 38 ··· 98  다음»