do_fork
    p = copy_process(clone_flags, stack_start, regs, stack_size,
             child_tidptr, NULL, trace);


    retval = copy_signal(clone_flags, p);
    if (retval)
        goto bad_fork_cleanup_sighand;
    retval = copy_mm(clone_flags, p);
    if (retval)
        goto bad_fork_cleanup_signal;
    retval = copy_namespaces(clone_flags, p);
    if (retval)
        goto bad_fork_cleanup_mm;
    retval = copy_io(clone_flags, p);
    if (retval)
        goto bad_fork_cleanup_namespaces;
    retval = copy_thread(clone_flags, stack_start, stack_size, p, regs);

// 현재 task(current)에서 mm을 두번째 파마리터에 복사를 함.
static int copy_mm(unsigned long clone_flags, struct task_struct *tsk)
{
    struct mm_struct *mm, *oldmm;
    int retval;

    tsk->min_flt = tsk->maj_flt = 0;
    tsk->nvcsw = tsk->nivcsw = 0;
#ifdef CONFIG_DETECT_HUNG_TASK
    tsk->last_switch_count = tsk->nvcsw + tsk->nivcsw;
#endif

    tsk->mm = NULL;
    tsk->active_mm = NULL;

    /*
     * Are we cloning a kernel thread?
     *
     * We need to steal a active VM for that..
     */
    oldmm = current->mm;
    if (!oldmm)
        return 0;

    if (clone_flags & CLONE_VM) {
        atomic_inc(&oldmm->mm_users);
        mm = oldmm;  // 핵심 루틴
        goto good_mm;
    }

    retval = -ENOMEM;
    mm = dup_mm(tsk);
    if (!mm)
        goto fail_nomem;

good_mm:
    /* Initializing for Swap token stuff */
    mm->token_priority = 0;
    mm->last_interval = 0;

    tsk->mm = mm;
    tsk->active_mm = mm;
    return 0;

fail_nomem:
    return retval;
}

'Core BSP 분석 > 리눅스 커널 핵심 분석' 카테고리의 다른 글

MMU - 메모리 설정  (0) 2023.05.05
커널패닉 - ext4 파일 시스템  (0) 2023.05.05
커널 에러 로그(1)  (0) 2023.05.05
hrtimer_restart, alarm timer  (0) 2023.05.05
바인더가 깨질 때의 콜 스택  (0) 2023.05.05

page allocation failure
This log indicates that total chunk memory is starved.
<4>[480188.689067 04-22 03:32:58.097] m.lge.launcher2: page allocation failure: order:1, mode:0xd0
<4>[480188.689123 04-22 03:32:58.097] [<c0016960>] (unwind_backtrace+0x0/0x144) from [<c083a788>] (dump_stack+0x20/0x24)
<4>[480188.689148 04-22 03:32:58.097] [<c083a788>] (dump_stack+0x20/0x24) from [<c011ebb4>] (warn_alloc_failed+0xd0/0x110)
<4>[480188.689170 04-22 03:32:58.097] [<c011ebb4>] (warn_alloc_failed+0xd0/0x110) from [<c01218b4>] (__alloc_pages_nodemask+0x5f0/0x828)
<4>[480188.689196 04-22 03:32:58.097] [<c01218b4>] (__alloc_pages_nodemask+0x5f0/0x828) from [<c00755ec>] (copy_process+0xe8/0xf04)
<4>[480188.689218 04-22 03:32:58.097] [<c00755ec>] (copy_process+0xe8/0xf04) from [<c0076534>] (do_fork+0xf0/0x358)
<4>[480188.689235 04-22 03:32:58.097] [<c0076534>] (do_fork+0xf0/0x358) from [<c0012eb4>] (sys_clone+0x3c/0x44)
<4>[480188.689254 04-22 03:32:58.097] [<c0012eb4>] (sys_clone+0x3c/0x44) from [<c000f040>] (ret_fast_syscall+0x0/0x30)
<4>[480188.689266 04-22 03:32:58.097] Mem-info:
<4>[480188.689274 04-22 03:32:58.097] Normal per-cpu:
<4>[480188.689286 04-22 03:32:58.097] CPU    0: hi:  186, btch:  31 usd:   0
<4>[480188.689297 04-22 03:32:58.097] CPU    1: hi:  186, btch:  31 usd:   0
<4>[480188.689343 04-22 03:32:58.097] CPU    2: hi:  186, btch:  31 usd:   0
<4>[480188.689355 04-22 03:32:58.097] CPU    3: hi:  186, btch:  31 usd:   0
<4>[480188.689367 04-22 03:32:58.097] HighMem per-cpu:
<4>[480188.689376 04-22 03:32:58.097] CPU    0: hi:   90, btch:  15 usd:   0
<4>[480188.689385 04-22 03:32:58.097] CPU    1: hi:   90, btch:  15 usd:   0
<4>[480188.689396 04-22 03:32:58.097] CPU    2: hi:   90, btch:  15 usd:   0
<4>[480188.689406 04-22 03:32:58.097] CPU    3: hi:   90, btch:  15 usd:   0
<4>[480188.689422 04-22 03:32:58.097] active_anon:105900 inactive_anon:11988 isolated_anon:5
<4>[480188.689429 04-22 03:32:58.097]  active_file:6691 inactive_file:7685 isolated_file:0
<4>[480188.689434 04-22 03:32:58.097]  unevictable:916 dirty:17 writeback:2 unstable:0
<4>[480188.689440 04-22 03:32:58.097]  free:3538 slab_reclaimable:3458 slab_unreclaimable:6362
<4>[480188.689446 04-22 03:32:58.097]  mapped:20938 shmem:12043 pagetables:1854 bounce:0
<4>[480188.689476 04-22 03:32:58.097] Normal free:13700kB min:2756kB low:3444kB high:4132kB active_anon:258628kB inactive_anon:1428kB active_file:17704kB inactive_file:20184kB unevictable:3628kB isolated(anon):20kB isolated(file):0kB present:576520kB mlocked:0kB dirty:60kB writeback:0kB mapped:24640kB shmem:1512kB slab_reclaimable:13832kB slab_unreclaimable:25448kB kernel_stack:4360kB pagetables:7416kB unstable:0kB bounce:0kB writeback_tmp:0kB pages_scanned:0 all_unreclaimable? no
<4>[480188.689502 04-22 03:32:58.097] lowmem_reserve[]: 0 2426 2426
<4>[480188.689530 04-22 03:32:58.097] HighMem free:452kB min:300kB low:668kB high:1040kB active_anon:164972kB inactive_anon:46524kB active_file:9060kB inactive_file:10556kB unevictable:36kB isolated(anon):0kB isolated(file):0kB present:310552kB mlocked:0kB dirty:8kB writeback:8kB mapped:59112kB shmem:46660kB slab_reclaimable:0kB slab_unreclaimable:0kB kernel_stack:0kB pagetables:0kB unstable:0kB bounce:0kB writeback_tmp:0kB pages_scanned:0 all_unreclaimable? no
<4>[480188.689557 04-22 03:32:58.097] lowmem_reserve[]: 0 0 0
<4>[480188.689572 04-22 03:32:58.097] Normal: 3404*4kB 7*8kB 2*16kB 0*32kB 0*64kB 0*128kB 0*256kB 0*512kB 0*1024kB 0*2048kB 0*4096kB = 13704kB
<4>[480188.689611 04-22 03:32:58.097] HighMem: 136*4kB 5*8kB 0*16kB 0*32kB 0*64kB 0*128kB 0*256kB 0*512kB 0*1024kB 0*2048kB 0*4096kB = 584kB
<4>[480188.689652 04-22 03:32:58.100] 27287 total pagecache pages

lowmemorykiller
The most popular log seen in the kernel log showing that lowmemory log is working.
<6>[480190.602177 04-22 03:33:00.012] lowmemorykiller: send sigkill to 3020 (m.lge.launcher2<zygote>), adj 0, size 6943
<6>[480190.684210 04-22 03:33:00.094] lowmemorykiller: send sigkill to 3033 (gle.android.gms<zygote>), adj 0, size 6812
<6>[480190.685726 04-22 03:33:00.096] lowmemorykiller: send sigkill to 3001 (u.browser.inter<zygote>), adj 0, size 6404

SysRq : Show Blocked State
The watchdog daemon in the userspace triggers this message via /proc/sysrq-trigger.
With this log, the call stack of uninterruptbile process is accessible.
<6>[483676.051211 04-22 04:31:05.461] SysRq : Show Blocked State
<6>[483676.051232 04-22 04:31:05.461]   task                PC stack   pid father
<6>[483676.051251 04-22 04:31:05.461] bbc-pm          D c0841ddc     0    36      2 0x00000000
<4>[483676.051310 04-22 04:31:05.461] [<c0841ddc>] (__schedule+0x410/0x890) from [<c08423ac>] (schedule+0x40/0x80)
<4>[483676.051339 04-22 04:31:05.462] [<c08423ac>] (schedule+0x40/0x80) from [<c0067260>] (tegra_bb_emc_dvfs_thread+0x80/0x6ec)
<4>[483676.051368 04-22 04:31:05.462] [<c0067260>] (tegra_bb_emc_dvfs_thread+0x80/0x6ec) from [<c0098530>] (kthread+0xb0/0xbc)
<4>[483676.051396 04-22 04:31:05.462] [<c0098530>] (kthread+0xb0/0xbc) from [<c000fca4>] (kernel_thread_exit+0x0/0x8)

This is a good chance to look into timer mechanism ofkernel timer.

/**
* alarmtimer_fired - Handles alarm hrtimer being fired.
* @timer: pointer to hrtimer being run
*
* When a alarm timer fires, this runs through the timerqueue to
* see which alarms expired, and runs those. If there are more alarm
* timers queued for the future, we set the hrtimer to fire when
* when the next future alarm timer expires.
*/
static enum hrtimer_restart alarmtimer_fired(struct hrtimer *timer)
{
    struct alarm_base *base = container_of(timer, struct alarm_base, timer);
    struct timerqueue_node *next;
    unsigned long flags;
    ktime_t now;
    int ret = HRTIMER_NORESTART;
    int restart = ALARMTIMER_NORESTART;
 
    spin_lock_irqsave(&base->lock, flags);
    now = base->gettime();
    while ((next = timerqueue_getnext(&base->timerqueue))) {
        struct alarm *alarm;
        ktime_t expired = next->expires;
 
        if (expired.tv64 > now.tv64)
            break;
 
        alarm = container_of(next, struct alarm, node);
 
        timerqueue_del(&base->timerqueue, &alarm->node);
        alarm->state &= ~ALARMTIMER_STATE_ENQUEUED;
 
        alarm->state |= ALARMTIMER_STATE_CALLBACK;
        spin_unlock_irqrestore(&base->lock, flags);
        if (alarm->function)
            restart = alarm->function(alarm, now);
        spin_lock_irqsave(&base->lock, flags);
        alarm->state &= ~ALARMTIMER_STATE_CALLBACK;
 
        if (restart != ALARMTIMER_NORESTART) {
            timerqueue_add(&base->timerqueue, &alarm->node);
            alarm->state |= ALARMTIMER_STATE_ENQUEUED;
        }
    }
 
    if (next) {
        hrtimer_set_expires(&base->timer, next->expires);
        ret = HRTIMER_RESTART;
    }
    spin_unlock_irqrestore(&base->lock, flags);
 
    return ret;
 
}.

 

This is a good chance to look into timer mechanism ofkernel timer.

 

static long alarm_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    int rv = 0;
    unsigned long flags;
<snip>
case ANDROID_ALARM_SET_OLD:
    case ANDROID_ALARM_SET_AND_WAIT_OLD:
        if (get_user(new_alarm_time.tv_sec, (int __user *)arg)) {
            rv = -EFAULT;
            goto err1;
        }
        new_alarm_time.tv_nsec = 0;
        goto from_old_alarm_set;
 
    case ANDROID_ALARM_SET_AND_WAIT(0):
    case ANDROID_ALARM_SET(0):
        if (copy_from_user(&new_alarm_time, (void __user *)arg,
            sizeof(new_alarm_time))) {
            rv = -EFAULT;
            goto err1;
        }
from_old_alarm_set:
        spin_lock_irqsave(&alarm_slock, flags);
        pr_alarm(IO, "alarm %d set %ld.%09ld\n", alarm_type,
            new_alarm_time.tv_sec, new_alarm_time.tv_nsec);
        alarm_enabled |= alarm_type_mask;
        devalarm_start(&alarms[alarm_type],
            timespec_to_ktime(new_alarm_time));
        spin_unlock_irqrestore(&alarm_slock, flags);
        if (ANDROID_ALARM_BASE_CMD(cmd) != ANDROID_ALARM_SET_AND_WAIT(0)
            && cmd != ANDROID_ALARM_SET_AND_WAIT_OLD)
            break;
        /* fall though */
    case ANDROID_ALARM_WAIT:

 

 

static const struct file_operations alarm_fops = {
    .owner = THIS_MODULE,
    .unlocked_ioctl = alarm_ioctl,
    .open = alarm_open,
    .release = alarm_release,
};

 

This driver is accessible thru /dev/alarm.

static struct miscdevice alarm_device = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "alarm",
    .fops = &alarm_fops,
};

 

 

[ 1248.598756 04-23 15:18:45.380] [<c00bccd4>] (alarmtimer_fired+0x144/0x15c) from [<c00970ac>] (__run_hrtimer+0x98/0x304)
[ 1248.609786 04-23 15:18:45.380] [<c00970ac>] (__run_hrtimer+0x98/0x304) from [<c0097fb0>] (hrtimer_interrupt+0x134/0x2f0)
[ 1248.620908 04-23 15:18:45.380] [<c0097fb0>] (hrtimer_interrupt+0x134/0x2f0) from [<c0028e84>] (tegra_cputimer_interrupt+0x4c/0x54)
[ 1248.632924 04-23 15:18:45.380] [<c0028e84>] (tegra_cputimer_interrupt+0x4c/0x54) from [<c00e9450>] (handle_irq_event_percpu+0x78/0x2d8)
[ 1248.645365 04-23 15:18:45.380] [<c00e9450>] (handle_irq_event_percpu+0x78/0x2d8) from [<c00e96f4>] (handle_irq_event+0x44/0x64)
[ 1248.657087 04-23 15:18:45.380] [<c00e96f4>] (handle_irq_event+0x44/0x64) from [<c00ec480>] (handle_fasteoi_irq+0xc4/0x16c)
[ 1248.668375 04-23 15:18:45.380] [<c00ec480>] (handle_fasteoi_irq+0xc4/0x16c) from [<c00e8c6c>] (generic_handle_irq+0x30/0x44)
[ 1248.679844 04-23 15:18:45.380] [<c00e8c6c>] (generic_handle_irq+0x30/0x44) from [<c000f074>] (handle_IRQ+0x54/0xb4)
[ 1248.690514 04-23 15:18:45.380] [<c000f074>] (handle_IRQ+0x54/0xb4) from [<c00084cc>] (gic_handle_irq+0x2c/0x60)
[ 1248.700859 04-23 15:18:45.380] [<c00084cc>] (gic_handle_irq+0x2c/0x60) from [<c081d804>] (__irq_svc+0x44/0x78)

 

static void __run_hrtimer(struct hrtimer *timer, ktime_t *now)
{
    struct hrtimer_clock_base *base = timer->base;
    struct hrtimer_cpu_base *cpu_base = base->cpu_base;
    enum hrtimer_restart (*fn)(struct hrtimer *);
    int restart;
 
    WARN_ON(!irqs_disabled());
 
    debug_deactivate(timer);
    __remove_hrtimer(timer, base, HRTIMER_STATE_CALLBACK, 0);
    timer_stats_account_hrtimer(timer);
    fn = timer->function; // alarmtimer_fired is called thru this callback function.
<snip>

 

가끔 바인더의 transaction이 깨질 때 커널에서 에러 로그가 뜬다.
유저 스페이스 영역의 바인더에서 어떤 짓을 할 때 이런 에러가 뜰 수 있는 지 알아보자.

frameworks/native/libs/binder/BpBinder.cpp:161

status_t BpBinder::transact(
    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
    // Once a binder has died, it will never come back to life.
    if (mAlive) {
        status_t status = IPCThreadState::self()->transact(
            mHandle, code, data, reply, flags);
        if (status == DEAD_OBJECT) mAlive = 0;
        return status;
    }

    return DEAD_OBJECT;
}


frameworks/native/libs/binder/IPCThreadState.cpp

status_t IPCThreadState::transact(int32_t handle,
                                  uint32_t code, const Parcel& data,
                                  Parcel* reply, uint32_t flags)
{
    status_t err = data.errorCheck();

    flags |= TF_ACCEPT_FDS;

    IF_LOG_TRANSACTIONS() {
        TextOutput::Bundle _b(alog);
        alog << "BC_TRANSACTION thr " << (void*)pthread_self() << " / hand "
            << handle << " / code " << TypeCode(code) << ": "
            << indent << data << dedent << endl;
    }

    <snip>

    if ((flags & TF_ONE_WAY) == 0) {
        <snip>
        IF_LOG_TRANSACTIONS() {
            TextOutput::Bundle _b(alog);
            alog << "BR_REPLY thr " << (void*)pthread_self() << " / hand "
                << handle << ": ";
            if (reply) alog << indent << *reply << dedent << endl;
            else alog << "(none requested)" << endl;
        }
    } else {
        err = waitForResponse(NULL, NULL);
    }

    return err;
}


frameworks/native/libs/binder/IPCThreadState.cpp

status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
{
    int32_t cmd;
    int32_t err;

    while (1) {
        if ((err=talkWithDriver()) < NO_ERROR) break;
        err = mIn.errorCheck();
        if (err < NO_ERROR) break;
        if (mIn.dataAvail() == 0) continue;

        cmd = mIn.readInt32();


frameworks/native/libs/binder/IPCThreadState.cpp

status_t IPCThreadState::talkWithDriver(bool doReceive)
{
    if (mProcess->mDriverFD <= 0) {
        return -EBADF;
    }

    binder_write_read bwr;

    // Is the read buffer empty?
    const bool needRead = mIn.dataPosition() >= mIn.dataSize();
...
    do {
        IF_LOG_COMMANDS() {
            alog << "About to read/write, write size = " << mOut.dataSize() << endl;
        }
#if defined(HAVE_ANDROID_OS)
        if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
            err = NO_ERROR;
        else
            err = -errno;
#else
        err = INVALID_OPERATION;
#endif
        if (mProcess->mDriverFD <= 0) {
            err = -EBADF;
        }
        IF_LOG_COMMANDS() {
            alog << "Finished read/write, write size = " << mOut.dataSize() << endl;
        }
    } while (err == -EINTR);
...


static void binder_transaction(struct binder_proc *proc,
                   struct binder_thread *thread,
                   struct binder_transaction_data *tr, int reply)
{
    struct binder_transaction *t;
...
    if (reply) {
        in_reply_to = thread->transaction_stack;
        if (in_reply_to == NULL) {
            binder_user_error("binder: %d:%d got reply transaction "
                      "with no transaction stack\n",
                      proc->pid, thread->pid);
            return_error = BR_FAILED_REPLY;
            goto err_empty_call_stack;
        }
        binder_set_nice(in_reply_to->saved_priority);
        if (in_reply_to->to_thread != thread) {
            binder_user_error("binder: %d:%d got reply transaction "
                "with bad transaction stack,"
                " transaction %d has target %d:%d\n",
                proc->pid, thread->pid, in_reply_to->debug_id,
                in_reply_to->to_proc ?
                in_reply_to->to_proc->pid : 0,
                in_reply_to->to_thread ?
                in_reply_to->to_thread->pid : 0);
            return_error = BR_FAILED_REPLY;
            in_reply_to = NULL;
            goto err_bad_call_stack;
        }
        thread->transaction_stack = in_reply_to->to_parent;
        target_thread = in_reply_to->from;
        if (target_thread == NULL) {
            return_error = BR_DEAD_REPLY;
            goto err_dead_binder;
        }
        if (target_thread->transaction_stack != in_reply_to) {
            binder_user_error("binder: %d:%d got reply transaction "
                "with bad target transaction stack %d, "
                "expected %d\n",
                proc->pid, thread->pid,
                target_thread->transaction_stack ?
                target_thread->transaction_stack->debug_id : 0,
                in_reply_to->debug_id);
            return_error = BR_FAILED_REPLY;
            in_reply_to = NULL;
            target_thread = NULL;
            goto err_dead_binder;
        }
        target_proc = target_thread->proc;
    } else {
        if (tr->target.handle) {
            struct binder_ref *ref;
            ref = binder_get_ref(proc, tr->target.handle);
            if (ref == NULL) {
                binder_user_error("binder: %d:%d got "
                    "transaction to invalid handle\n",
                    proc->pid, thread->pid);
                return_error = BR_FAILED_REPLY;
                goto err_invalid_target_handle;
            }
            target_node = ref->node;
        } else {
            target_node = binder_context_mgr_node;
            if (target_node == NULL) {
                return_error = BR_DEAD_REPLY;
                goto err_no_context_mgr_node;
            }
        }

        e->to_node = target_node->debug_id;
        target_proc = target_node->proc;
        if (target_proc == NULL) {
            return_error = BR_DEAD_REPLY;
            goto err_dead_binder;
        }
        if (!(tr->flags & TF_ONE_WAY) && thread->transaction_stack) {
            struct binder_transaction *tmp;
            tmp = thread->transaction_stack;
            if (tmp->to_thread != thread) {
                binder_user_error("binder: %d:%d got new "
                    "transaction with bad transaction stack"
                    ", transaction %d has target %d:%d\n",
                    proc->pid, thread->pid, tmp->debug_id,
                    tmp->to_proc ? tmp->to_proc->pid : 0,
                    tmp->to_thread ?
                    tmp->to_thread->pid : 0);
                return_error = BR_FAILED_REPLY;
                goto err_bad_call_stack;
            }
            while (tmp) {
                if (tmp->from && tmp->from->proc == target_proc)
                    target_thread = tmp->from;
                tmp = tmp->from_parent;
            }
        }
    }
...

err_no_context_mgr_node:
    binder_debug(BINDER_DEBUG_FAILED_TRANSACTION,
             "binder: %d:%d transaction failed %d, size %zd-%zd\n",
             proc->pid, thread->pid, return_error,
             tr->data_size, tr->offsets_size);

    {
        struct binder_transaction_log_entry *fe;
        fe = binder_transaction_log_add(&binder_transaction_log_failed);
        *fe = *e;
    }

    BUG_ON(thread->return_error != BR_OK);
    if (in_reply_to) {
        thread->return_error = BR_TRANSACTION_COMPLETE;
        binder_send_failed_reply(in_reply_to, return_error);
    } else
        thread->return_error = return_error;
}

아이노드 오퍼레이션
iput(
    (register struct inode *) inode = 0xE7017A90 = __bss_stop+0x26161070 -> ((umode_t) i_mode = 33152 = 0x8180 = '..', (short unsigned int) i_opflags = 5 = 0x5 = '..',
  (register struct inode *) inode = 0xE7017A90 = __bss_stop+0x26161070 -> (
    (umode_t) i_mode = 33152 = 0x8180 = '..',
    (short unsigned int) i_opflags = 5 = 0x5 = '..',
    (uid_t) i_uid = 1000 = 0x03E8 = '....', //  소유자 사용자 아이디
    (gid_t) i_gid = 1000 = 0x03E8 = '....', // 소유자 그룹 아이디
    (unsigned int) i_flags = 2 = 0x2 = '....',
    (struct posix_acl *) i_acl = 0xFFFFFFFF = PRRR+0xF57657 -> ((atomic_t) a_refcount = ((int) counter = ERROR:MMUFAIL), (struct rcu_head) a_rcu = ((struct rcu_head *)
    (struct posix_acl *) i_default_acl = 0xFFFFFFFF = PRRR+0xF57657 -> ((atomic_t) a_refcount = ((int) counter = ERROR:MMUFAIL), (struct rcu_head) a_rcu = ((struct rcu_
    (struct inode_operations *) i_op = 0xC084DB00 = ext4_file_inode_operations -> ((struct dentry * (*)()) lookup = 0x0 = , (void * (*)()) follow_link = 0x0 = , (int (*
    (struct super_block *) i_sb = 0xE0E79800 = __bss_stop+0x1FFC2DE0 -> ((struct list_head) s_list = ((struct list_head *) next = 0xE0E79000 = __bss_stop+0x1FFC25E0, (s
    (struct address_space *) i_mapping = 0xE7017B68 = __bss_stop+0x26161148 -> ((struct inode *) host = 0xE7017A90 = __bss_stop+0x26161070, (struct radix_tree_root) pag
    (void *) i_security = 0xDFDCDA58 = __bss_stop+0x1EF17038 -> ,
    (long unsigned int) i_ino = 265595 = // 아이노드 번호
    (unsigned int) i_nlink = 0 = 0x0 = '....', //하드링크 갯수
    (unsigned int) __i_nlink = 0 = 0x0 = '....',
    (dev_t) i_rdev = 0 = 0x0 = '....',   // 실제 디바이스 노드
    (struct timespec) i_atime = // 마지막 접근 시간
    (struct timespec) i_mtime = // 마지막 수정 시간
    (struct timespec) i_ctime = // 마지막 변경 시간,
    (spinlock_t) i_lock = // 스핀락
    (short unsigned int) i_bytes = 0 = 0x0 = '..',
    (blkcnt_t) i_blocks = 0 = 0x0,
    (loff_t) i_size = 0 = 0x0,
    (seqcount_t) i_size_seqcount = ((unsigned int) sequence = 0 = 0x0 = '....'),
    (long unsigned int) i_state = 96 = 0x60 = '...`',
    (struct mutex) i_mutex = ((atomic_t) count = ((int) counter = 1 = 0x1 = '....'), (spinlock_t) wait_lock = ((struct raw_spinlock) rlock = ((arch_spinlock_t) raw_lock
    (long unsigned int) dirtied_when = 0 = 0x0 = '....',  // 최초 변경시간
    (struct hlist_node) i_hash = // 해시 리스트
    (struct list_head) i_wb_list = ((struct list_head *) next = 0xE7017B24 = __bss_stop+0x26161104 -> ((struct list_head *) next = 0xE7017B24 = __bss_stop+0x26161104, (
    (struct list_head) i_lru = ((struct list_head *) next = 0xE7017B2C = __bss_stop+0x2616110C -> ((struct list_head *) next = 0xE7017B2C = __bss_stop+0x2616110C, (stru
    (struct list_head) i_sb_list = ((struct list_head *) next = 0xE7017B34 = __bss_stop+0x26161114 -> ((struct list_head *) next = 0xE7017B34 = __bss_stop+0x26161114, (
    (struct list_head) i_dentry = ((struct list_head *) next = 0xE7017B3C = __bss_stop+0x2616111C -> ((struct list_head *) next = 0xE7017B3C = __bss_stop+0x2616111C, (s
    (struct rcu_head) i_rcu = ((struct rcu_head *) next = 0xE7017B3C = __bss_stop+0x2616111C -> ((struct rcu_head *) next = 0xE7017B3C = __bss_stop+0x2616111C, (void (*
    (atomic_t) i_count = ((int) counter = 0 = 0x0 = '....'), // 참조횟수
    (unsigned int) i_blkbits = 12 = 0x0C = '....',
    (u64) i_version = 1 = 0x1,  // 버젼 정보
    (atomic_t) i_dio_count = ((int) counter = 0 = 0x0 = '....'),
    (atomic_t) i_writecount = ((int) counter = 0 = 0x0 = '....'),
    (struct file_operations *) i_fop = 0xC084DB80 = ext4_file_operations -> ((struct module *) owner = 0x0 = , (loff_t (*)()) llseek = 0xC01E20C8 = ext4_llseek, (ssize_
    (struct file_lock *) i_flock = 0x0 =  -> NULL,
    (struct address_space) i_data = ((struct inode *) host = 0xE7017A90 = __bss_stop+0x26161070 -> ((umode_t) i_mode = 33152 = 0x8180 = '..', (short unsigned int) i_opf
    (struct list_head) i_devices = ((struct list_head *) next = 0xE7017BD4 = __bss_stop+0x261611B4 -> ((struct list_head *) next = 0xE7017BD4 = __bss_stop+0x261611B4, (
    (struct pipe_inode_info *) i_pipe = 0x0 =  -> NULL,
    (struct block_device *) i_bdev = 0x0 =  -> NULL,
    (struct cdev *) i_cdev = 0x0 =  -> NULL, // 캐릭터 장치 드라이버
    (__u32) i_generation = 3015412147 = 0xB3BB89B3 = '....',
    (__u32) i_fsnotify_mask = 0 = 0x0 = '....',
    (struct hlist_head) i_fsnotify_marks = ((struct hlist_node *) first = 0x0 =  -> NULL),
    (void *) i_private = 0x0 =  -> NULL)
  (register struct super_block *) sb = 0xE0E79800 = __bss_stop+0x1FFC2DE0 -> (
    (struct list_head) s_list = ((struct list_head *) next = 0xE0E79000 = __bss_stop+0x1FFC25E0 -> ((struct list_head *) next = 0xDFE3EC00 = __bss_stop+0x1EF881E0, (str
    (dev_t) s_dev = 271581186 = 0x10300002 = '.0..',
    (unsigned char) s_dirt = 0 = 0x0 = '.',
    (unsigned char) s_blocksize_bits = 12 = 0x0C = '.',

아이노드 동작 오퍼레이션
    (struct inode_operations *) i_op = 0xC084DB00 = ext4_file_inode_operations -> (
      (struct dentry * (*)()) lookup = 0x0 =  -> NULL,
      (void * (*)()) follow_link = 0x0 =  -> NULL,
      (int (*)()) permission = 0x0 =  -> NULL,
      (struct posix_acl * (*)()) get_acl = 0xC0225294 = ext4_get_acl -> ,
      (int (*)()) readlink = 0x0 =  -> NULL,
      (void (*)()) put_link = 0x0 =  -> NULL,
      (int (*)()) create = 0x0 =  -> NULL,
      (int (*)()) link = 0x0 =  -> NULL,
      (int (*)()) unlink = 0x0 =  -> NULL,
      (int (*)()) symlink = 0x0 =  -> NULL,
      (int (*)()) mkdir = 0x0 =  -> NULL,
      (int (*)()) rmdir = 0x0 =  -> NULL,
      (int (*)()) mknod = 0x0 =  -> NULL,
      (int (*)()) rename = 0x0 =  -> NULL,
      (void (*)()) truncate = 0x0 =  -> NULL,
      (int (*)()) setattr = 0xC01E99A4 = ext4_setattr -> ,
      (int (*)()) getattr = 0xC01E7EB8 = ext4_getattr -> ,
      (int (*)()) setxattr = 0xC0178FFC = generic_setxattr -> ,
      (ssize_t (*)()) getxattr = 0xC0178E98 = generic_getxattr -> ,
      (ssize_t (*)()) listxattr = 0xC0223864 = ext4_listxattr -> ,
      (int (*)()) removexattr = 0xC0179080 = generic_removexattr -> ,
      (void (*)()) truncate_range = 0x0 =  -> NULL,
      (int (*)()) fiemap = 0xC02128C0 = ext4_fiemap -> ),

커널 패닉이 발생했다. 우선 콜 스택을 확인해보자.
-000|ext4_free_inode(handle = 0xE09382C0, ?)
-001|ext4_evict_inode(inode = 0xE7017A90)
-002|evict(inode = 0xE7017A90)
-003|iput(inode = 0xE7017A90)
-004|do_unlinkat(?, ?)
-005|sys_unlink(?)
-006|ret_fast_syscall(asm)
 -->|exception
-007|NUR:0x3A0:0x40067B78(asm)
-008|NUT:0x3A0:0x4005C330(asm)
-009|NUT:0x3A0:0x5A559380(asm)
-010|NUR:0x3A0:0x4163C550(asm)
-011|NUT:0x3A0:0x4166CCA6(asm)
-012|NUR:0x3A0:0x41645964(asm)
 ---|end of frame

do_unlinkat() stack으로 돌아가서 어느 파일로 접근을 하다가 패닉이 발생하였는 지 확인하자.
각각 덴트리 조각들으 모아서 파일 경로를 확인하면 아래와 같다.
          (struct list_head) d_lru = ((struct list_head *) next = 0xE0915424 = __bss_stop+0x1FA5EA04 -> ((struct list_head *) next = 0xE0915424 = __bss_stop+0x1FA5EA04, (struct list_
          (union) d_u = ((struct list_head) d_child = ((struct list_head *) next = 0xE091542C = __bss_stop+0x1FA5EA0C -> ((struct list_head *) next = 0xE091542C = __bss_stop+0x1FA5EA
          (struct list_head) d_subdirs = ((struct list_head *) next = 0xE0997B9C = __bss_stop+0x1FAE117C -> ((struct list_head *) next = 0xE0AD28F4 = __bss_stop+0x1FC1BED4, (struct l
          (struct list_head) d_alias = ((struct list_head *) next = 0xE0930DEC = __bss_stop+0x1FA7A3CC -> ((struct list_head *) next = 0xE091543C = __bss_stop+0x1FA5EA1C, (struct lis
        (struct qstr) d_name = ((unsigned int) hash = 3065167468 = 0xB6B2BE6C = '...l', (unsigned int) len = 6 = 0x6 = '....', (unsigned char *) name = 0xE0997B54 = __bss_stop+0x1FAE
        (struct inode *) d_inode = 0xE9D2F400 = __bss_stop+0x28E789E0 -> ((umode_t) i_mode = 16832 = 0x41C0 = 'A.', (short unsigned int) i_opflags = 7 = 0x7 = '..', (uid_t) i_uid = 1
        (unsigned char [36]) d_iname = "backup",
        (unsigned int) d_count = 4 = 0x4 = '....',
        (spinlock_t) d_lock = ((struct raw_spinlock) rlock = ((arch_spinlock_t) raw_lock = ((unsigned int) lock = 0 = 0x0 = '....'), (unsigned int) break_lock = 0 = 0x0 = '....')),
...
      (struct qstr) d_name = ((unsigned int) hash = 3881559661 = 0xE75BE66D = '.[.m', (unsigned int) len = 7 = 0x7 = '....', (unsigned char *) name = 0xE701E57C = __bss_stop+0x26167B
      (struct inode *) d_inode = 0xE9D2F190 = __bss_stop+0x28E78770 -> ((umode_t) i_mode = 16832 = 0x41C0 = 'A.', (short unsigned int) i_opflags = 7 = 0x7 = '..', (uid_t) i_uid = 100
      (unsigned char [36]) d_iname = "pending",
      (unsigned int) d_count = 3 = 0x3 = '....',
...
      (struct list_head) d_alias = ((struct list_head *) next = 0xE9D2F23C = __bss_stop+0x28E7881C -> ((struct list_head *) next = 0xE701E5D4 = __bss_stop+0x26167BB4, (struct list_he
    (struct qstr) d_name = ((unsigned int) hash = 3056702157 = 0xB63192CD = '.1..', (unsigned int) len = 20 = 0x14 = '....', (unsigned char *) name = 0xE7025824 = __bss_stop+0x2616EE
    (struct inode *) d_inode = 0x0 =  -> NULL,
    (unsigned char [36]) d_iname = "journal631699301.tmp",
    (unsigned int) d_count = 0 = 0x0 = '....',

///data/backup/pendig/journal631699301.tmp 이 파일에 접근하려는 것이었다.
SYSCALL_DEFINE1(unlink, const char __user *, pathname)
{
    return do_unlinkat(AT_FDCWD, pathname);
}

슈퍼블록 객체를 알아보자. 참고로 ext4 파일시스템이다.

iput(
        (struct list_head) s_list = ((struct list_head *) next = 0xE0E79000 // 슈퍼 블록 리스트. 이 주소를 통해서 다른 파일 시스템의 슈퍼 블록에 접근을 할 수 있을 것 같다.
        (dev_t) s_dev = 271581186 = 0x10300002 = '.0..', // 식별자임
        (unsigned char) s_dirt = 0 = 0x0 = '.',
        (unsigned char) s_blocksize_bits = 12 = 0x0C = '.' // 비트 단위의 블록 크기: 12, 즉 0x0C 비트를 left shift 연산 해야 블락 사이즈(단위는 바이트)를 얻을 수 있음
        (long unsigned int) s_blocksize = 4096 = 0x1000 = '....',// 바이트 단위의 블록 크기
        (loff_t) s_maxbytes = 2199023251456 = 0x000001FFFFFFF000, // 최대 파일 크기
        (struct file_system_type *) s_type = 0xC0CAAD6C = ext4_fs_type -> ((char *) name = 0xC0A297FC =  -> "ext4", // 파일 시스템 유형 
        (struct super_operations *) s_op = 0xC084E890 = // 슈퍼블록 함수
        (struct dquot_operations *) dq_op = 0x0 =  -> NULL, // 사용량 제한 함수
        (struct quotactl_ops *) s_qcop = 0x0 =  -> NULL, // 사용량 제어 함수
        (struct export_operations *) s_export_op = 0xC084E9B0 = // 파일 시스템 외부 제공 함수
        (long unsigned int) s_flags = 1879113728 = 0x70010000 = 'p...', // 마운트 플래그
        (long unsigned int) s_magic = 61267 = 0xEF53 = '...S', // 파일 시스템 고유 번호
        (struct dentry *) s_root = 0xE09153C0 = // 디렉토리 마운트 지점
        (struct rw_semaphore) s_umount = // 마운트 해제용 세마포어
        (struct mutex) s_lock =         // 슈퍼 블락 세마포어
        (int) s_count = 1 = 0x1 = '....',     // 슈퍼블록 참조 횟수
        (atomic_t) s_active = ((int) counter = 2 = 0x2 = '....'),
        (void *) s_security = 0xDFEC2B80 = __bss_stop+0x1F00C160 -> ,
        (struct xattr_handler * *) s_xattr =     // 확장 속성 핸들러
        (struct list_head) s_inodes = ((struct list_head *) next // 아이노드 리스트
        (struct hlist_bl_head) s_anon = ((struct hlist_bl_node *) first = 0x0 =  -> NULL), //익명 디렉토리 항목
        (struct list_head *) s_files = 0xC0C30ABC = irq_desc[54].depth // 할당된 파일 리스트
        (struct list_head) s_mounts = ((struct list_head *) next = 0xE76E4A74 = __bss_stop+0x2682E054 -> ((struct list_head *) next = 0xDFEA6574 = __bss_stop+0x1EFEFB54
        (struct list_head) s_dentry_lru = ((struct list_head *) next = 0xE7025864 = // 미사용 덴트리 항목 리스트
        (int) s_nr_dentry_unused = 963 = 0x03C3 = '....',
        (spinlock_t) s_inode_lru_lock = ((struct raw_spinlock) rlock = ((arch_spinlock_t) raw_lock = ((unsigned int) lock = 0 = 0x0 = '....'), (unsigned int) break_lock
        (struct list_head) s_inode_lru = ((struct list_head *) next = 0xE0E798C8 = 
        (int) s_nr_inodes_unused = 0 = 0x0 = '....',
        (struct block_device *) s_bdev = 0xE08FFC80 = //관련 블록 디바이스
        (struct backing_dev_info *) s_bdi = 0xE600B3D8 = __bss_stop+0x251549B8 -> ((struct list_head) bdi_list = ((struct list_head *) next = 0xE600AF40 = __bss_stop+0x
        (struct mtd_info *) s_mtd = 0x0 =  -> NULL, // 메모리 디스크 정보
        (struct hlist_node) s_instances = ((struct hlist_node *) next = 0xE76F5CE0 // 같은 파일 시스템 인스턴스
        (struct quota_info) s_dquot = ((unsigned int) flags = 0 //사용량 제한 옵션
        (int) s_frozen = 0 = 0x0 = '....',  // 동결 상태
        (wait_queue_head_t) s_wait_unfrozen = // 동결 상태에 있는 웨이트 큐
        (char [32]) s_id = "mmcblk0p18", // 이름 문자열
        (u8 [16]) s_uuid = "W.....e_.g.o...[",
        (void *) s_fs_info = 0xE0E79C00 = // 파일 시스템 관련 정보
        (unsigned int) s_max_links = 0 = 0x0 = '....',
        (fmode_t) s_mode = 131 = 0x83 = '....',
        (u32) s_time_gran = 1 = 0x1 = '....', //타임 스탬프 정밀도
        (struct mutex) s_vfs_rename_mutex = ((atomic_t) count = ((int) counter = 1 = 0x1 = '....'), (spinlock_t) wait_lock = ((struct raw_spinlock) rlock = ((arch_spinl
        (char *) s_subtype = 0x0 =  -> NULL,// 하부 유형 이름
        (char *) s_options = 0x0 =  -> NULL, // 저장된 마운트 옵션
 
슈퍼블락 오퍼레이션
       (struct super_operations *) s_op = 0xC084E890 = ext4_sops -> (
          (struct inode * (*)()) alloc_inode = 0xC01F72D4 = ext4_alloc_inode -> ,
          (void (*)()) destroy_inode = 0xC0203074 = ext4_destroy_inode -> ,
          (void (*)()) dirty_inode = 0xC01EC7B0 = ext4_dirty_inode -> ,
          (int (*)()) write_inode = 0xC01E7D9C = ext4_write_inode -> ,
          (int (*)()) drop_inode = 0xC01FDFDC = ext4_drop_inode -> ,
          (void (*)()) evict_inode = 0xC01EC2F0 = ext4_evict_inode -> ,
          (void (*)()) put_super = 0xC0204038 = ext4_put_super -> ,
          (void (*)()) write_super = 0x0 =  -> NULL,
          (int (*)()) sync_fs = 0xC01F71B8 = ext4_sync_fs -> ,
          (int (*)()) freeze_fs = 0xC0202F28 = ext4_freeze -> ,
          (int (*)()) unfreeze_fs = 0xC0202ED0 = ext4_unfreeze -> ,
          (int (*)()) statfs = 0xC01F68A0 = ext4_statfs -> ,
          (int (*)()) remount_fs = 0xC02049A0 = ext4_remount -> ,
          (void (*)()) umount_begin = 0x0 =  -> NULL,
          (int (*)()) show_options = 0xC01F7110 = ext4_show_options -> ,
          (int (*)()) show_devname = 0x0 =  -> NULL,
          (int (*)()) show_path = 0x0 =  -> NULL,
          (int (*)()) show_stats = 0x0 =  -> NULL,
          (int (*)()) bdev_try_to_free_page = 0xC01F6B64 = bdev_try_to_free_page -> ,
          (int (*)()) nr_cached_objects = 0x0 =  -> NULL,
          (void (*)()) free_cached_objects = 0x0 =  -> NULL),
        (struct dquot_operations *) dq_op = 0x0 =  -> NULL,
        (struct quotactl_ops *) s_qcop = 0x0 =  -> NULL,
 
 
디렉토리 마운트 지점
       (struct dentry *) s_root = 0xE09153C0 = __bss_stop+0x1FA5E9A0 -> (
          (unsigned int) d_flags = 0 = 0x0 = '....',
          (seqcount_t) d_seq = ((unsigned int) sequence = 2 = 0x2 = '....'),
          (struct hlist_bl_node) d_hash = ((struct hlist_bl_node *) next = 0x0 =  -> NULL, (struct hlist_bl_node * *) pprev = 0x0 =  -> NULL),
          (struct dentry *) d_parent = 0xE09153C0 = __bss_stop+0x1FA5E9A0 -> ((unsigned int) d_flags = 0 = 0x0 = '....', (seqcount_t) d_seq = ((unsigned int) sequence =
          (struct qstr) d_name = ((unsigned int) hash = 0 = 0x0 = '....', (unsigned int) len = 1 = 0x1 = '....', (unsigned char *) name = 0xE09153E4 = __bss_stop+0x1FA5
          (struct inode *) d_inode = 0xE0930D40 = __bss_stop+0x1FA7A320 -> ((umode_t) i_mode = 16889 = 0x41F9 = 'A.', (short unsigned int) i_opflags = 7 = 0x7 = '..', (
          (unsigned char [36]) d_iname = "/",
          (unsigned int) d_count = 42 = 0x2A = '...*',
          (spinlock_t) d_lock = ((struct raw_spinlock) rlock = ((arch_spinlock_t) raw_lock = ((unsigned int) lock = 0 = 0x0 = '....'), (unsigned int) break_lock = 0 = 0
          (struct dentry_operations *) d_op = 0x0 =  -> NULL,
          (struct super_block *) d_sb = 0xE0E79800 = __bss_stop+0x1FFC2DE0 -> ((struct list_head) s_list = ((struct list_head *) next = 0xE0E79000 = __bss_stop+0x1FFC25
          (long unsigned int) d_time = 0 = 0x0 = '....',
          (void *) d_fsdata = 0x0 =  -> NULL,
          (struct list_head) d_lru = ((struct list_head *) next = 0xE0915424 = __bss_stop+0x1FA5EA04 -> ((struct list_head *) next = 0xE0915424 = __bss_stop+0x1FA5EA04,
          (union) d_u = ((struct list_head) d_child = ((struct list_head *) next = 0xE091542C = __bss_stop+0x1FA5EA0C -> ((struct list_head *) next = 0xE091542C = __bss
          (struct list_head) d_subdirs = ((struct list_head *) next = 0xE0997B9C = __bss_stop+0x1FAE117C -> ((struct list_head *) next = 0xE0AD28F4 = __bss_stop+0x1FC1B
          (struct list_head) d_alias = ((struct list_head *) next = 0xE0930DEC = __bss_stop+0x1FA7A3CC -> ((struct list_head *) next = 0xE091543C = __bss_stop+0x1FA5EA1
 

unlink라는 시스템 콜을 따라가면 아래 함수를 볼 수 있다.
여기서 lookup_hash(&nd) 함수를 좀 파고들어가 보자.

static long do_unlinkat(int dfd, const char __user *pathname)
{
    int error;
    char *name;
    struct dentry *dentry;
    struct nameidata nd;
    struct inode *inode = NULL;
...
    nd.flags &= ~LOOKUP_PARENT;

    mutex_lock_nested(&nd.path.dentry->d_inode->i_mutex, I_MUTEX_PARENT);
    dentry = lookup_hash(&nd);
}

아래와 같은 함수 호출 체인을 볼 수 있는데, 덴트리는 덴트리 캐시에서 덴트리 인스턴스를 찾는 것이다.
static struct dentry *lookup_hash(struct nameidata *nd)
{
    return __lookup_hash(&nd->last, nd->path.dentry, nd);
}

static struct dentry *__lookup_hash(struct qstr *name,
        struct dentry *base, struct nameidata *nd)
{
    bool need_lookup;
    struct dentry *dentry;

    dentry = lookup_dcache(name, base, nd, &need_lookup);
    if (!need_lookup)
        return dentry;

    return lookup_real(base->d_inode, dentry, nd);
}

d_hash(parent, hash); 라는 함수를 호출해서 해시 테이블 포멧의 덴트리 캐시 리스트를 가져온다.

static struct dentry *lookup_dcache(struct qstr *name, struct dentry *dir,
                    struct nameidata *nd, bool *need_lookup)
{
    struct dentry *dentry;
    int error;

    *need_lookup = false;
    dentry = d_lookup(dir, name);
..
}
struct dentry *d_lookup(struct dentry *parent, struct qstr *name)
{
    struct dentry *dentry;
    unsigned seq;

        do {
                seq = read_seqbegin(&rename_lock);
                dentry = __d_lookup(parent, name);
                if (dentry)
            break;
    } while (read_seqretry(&rename_lock, seq));
    return dentry;
}

struct dentry *__d_lookup(struct dentry *parent, struct qstr *name)
{
    unsigned int len = name->len;
    unsigned int hash = name->hash;
    const unsigned char *str = name->name;
    struct hlist_bl_head *b = d_hash(parent, hash);
    struct hlist_bl_node *node;
..
}

결국 *d_hash() 함수를 통해 덴트리 캐시를 처리하는 해시 테이블에 접근한다.
dentry_hashtable은 0xC17C8200에 있다.
  (static struct hlist_bl_head *) dentry_hashtable = 0xC17C8200 = __bss_stop+0x9117E0 -> (
    (struct hlist_bl_node *) first = 0x0 =  -> NULL)
static inline struct hlist_bl_head *d_hash(const struct dentry *parent,
                    unsigned int hash)
{
    hash += (unsigned long) parent / L1_CACHE_BYTES;
    hash = hash + (hash >> D_HASHBITS);
    return dentry_hashtable + (hash & D_HASHMASK);
}

0xC17C8200 란 주소와 이 리스트의 구조체만 가지고 리스트의 내용을 깔 수가 있는데 다음에 찾자.

이번에는 GPIO에 대해 이야기 좀 해보려 합니다.
 
한 10년 전인가요? 기억이 좀 가물 가물한데요. 대학원 면접을 봤었어요. 그 때 어느 교수님이 했던 질문이 생각납니다.
 
     "소프트웨어와 하드웨어의 경계가 어디냐"? 
 
그 당시 너무 얼어 있어 제대로 대답을 못했던 것 같습니다. 결국 대답을 못하고 말았습니다. 지금 생각해 보면 정말 쉬운 질문이었는데 참 안타깝죠.
 
만약 지금 그 질문을 받으면 전 이렇게 대답할 것입니다.
 
     "GPIO와 인터럽트입니다."
 
물론 하드웨어와 소프트웨어의 경계가 GPIO와 인터럽트만 있는 것은 아닙니다. 
메모리 맵드 아이오(memory mapped IO)도 있죠. 특정 메모리 공간에 어떤 값을 쓰면 이에 맞게 하드웨어 디바이스가 동작하는 것입니다.
 
이제 좀 GPIO에 대해서 조금 더 짚어보겠습니다.

보통 MCU에는 본연의 임무를 하는 pin들이 있습니다. 

     "Hardware적으로 정해져 있는 CS, WE라든가 하는 정해진 일을 하는 pin
    어떤 특별한 임무를 갖지 않고, 유저가 원하는 대로 I/O로 사용할 수 있는 pin" 

그런데, 재미있게도 GPIO란 핀은 여러가지 기능을 제공합니다. 다용도로 쓸 수 있다는 것입니다. 축구 선수를 스트라이커, 수비수, 골키퍼로 사용할 수 있는 것과 비슷한 것 같습니다.

좀 더 기술적으로 예를 들어볼까요? 
 
     "어떤 pin은 어떤 chip에 대한 CS로 사용할 수도 있지만, GPIO로도 쓸 수 있다."
 
 이것을 임베디드 용어로 Alternative Functionality라고 말합니다. 그런데 어떤 pin은 이런 Alternative Functionality를 갖는가 하면, GPIO 전용으로만 쓰이는 pin들이 있습니다. 이 정보를 제대로 알려면 MCU의 데이터 쉬트(Data Sheet)나 회로도를 꼭 읽어봐야 합니다.
 
이런 것을 GPIO라고 부르는데, GPIO라는 건 GP + I/O라는 뜻입니다. GPIO는 말 그대로 , IO니까, Input을 받거나(GPIO pin 상태를 읽고 싶거나), Output을 내놓거나(GPIO pin에 뭔가 쓰고 싶거나)하는 데 쓰입니다. 이런 GPIO는 MCU하나에 적으면 10개 많으면 200개까지 준비되어 있습니다.

ARM 기반 MCU입장에서 GPIO에 대해서 알아볼까요? 
 
     "GPIO는 AMBA bus에 연결된 SOC Peripheral이다." 
     "GPIO는 APB bus에 연결된 Slave이다. "
 
그래서 GPIO들도 Register를 가지고 있고, CPU입장에서는 Register를 통해서 control 가능합니다. 
 
그 제어(control)이라는 게 I/O로 쓸 수 있다고 했으니까, 어떤 때는 인풋(Input) 어떤 때는 아웃풋(Output)으로 써야 합니다. 이런 I/O는 레지스터(Register)를 통해서 Programmable할 수 있습니다. Input, Output으로 원하는 대로 사용 가능해야 하니까. 이런 레지스터의 구조는 물론 MCU마다 당연히 달라요. 퀄컴, 인텔, 엔비디아 서로 다른 구조로 설계를 했다는 것이지요. 그러니 data sheet를 잘 읽어야 합니다.

Register를 통해서 GPIO를 control해야 하는 것에는, 3가지가 있어요. 이 3가지가 모두 제대로 설정되어야 GPIO를 내 수족처럼 control할 수 있어요.
 
1) Pin의 Mode
   → GPIO로 쓸 꺼냐, Alternative Functionality로 쓸 꺼냐.
   → Alternative로 설정하게 되면 GPIO로는 못쓴다. 
   → 게임 오바 되면 Hardware적으로 정해진 임무를 수행. 신경 꺼도 됨

2) Pin의 상태 (Mode)를 활성화 하면서 Data Direction을 정할 수 있다.
   → 지금부터 Pin이 사용 가능하도록 Init 하겠다.
   → Input이냐, Output이냐를 결정하겠다.

3) 자~ 읽어 보자 라든가, 자~ 값을 써보자를 할 수 있다.

리눅스 커널 API

리눅스 드라이버 레벨에서 GPIO를 제어 하기 위해 리눅스 커널에서는 다음 API를 제공합니다.
필요한 헤더파일   #include <asm/gpio.h>
GPIO 입력설정     gpio_direction_input( gp_nr );
GPIO 출력설정     gpio_direction_ouput( gp_nr,  init_val );   // init_val 는 초기값이다.
GPIO 출력            gpio_set_value( gp_nr, val );                   // val 는 0, 1 값이다.
GPIO 입력            gpio_get_value( gp_nr );
GPIO 인터럽트 활성화   set_irq_type( irq_nr, irq_type );           
 
그런데, 이렇게 API에 대한 설명만 읽고는 실무에 활용할 수 없습니다. 상세한 시나리오를 만들어서 실제 API가 어디서 사용되는 지 알아야 머리에 오래 남고 다른 드라이버에 활용하기 쉽습니다.


GPIO 출력 모드

아래는 GPIO를 출력 모드로 설정해서 사용하고 싶을 때의 예시 코드입니다.
GPIO를 출력 모드로 사용한다는 것은 특정 상황에서 GPIO을 0 혹은 1로 바꾸고 싶을 경우에 주로 사용합니다.

(참고로 아래 udelay(1) 만큼 딜레이를 주는 코드가 칫셉 벤더에서 전달이 되면, 절대 바꾸면 안됩니다. 이게 다 데이터 시트에 있는 것을 구현하는 것이기 때문입니다.)
#define GPIO_NUMBER 11 
int gpio_nreset = GPIO_NUMBER;
 
static int cs4271_probe(struct snd_soc_codec *codec)
{
    struct cs4271_private *cs4271 = snd_soc_codec_get_drvdata(codec);
    struct cs4271_platform_data *cs4271plat = codec->dev->platform_data;
    int ret;
 
    //GPIO 핀을 초기화
        if (gpio_request(gpio_nreset, "CS4271 Reset"))
            gpio_nreset = -EINVAL;
 
    // GPIO를 출력(아웃풋)모드로 변경
        gpio_direction_output(gpio_nreset, 0);
 
    // 나노 초만큼 딜레이를 줌
        udelay(1);
 
    // GPIO 핀을 1로 올려 줌 
        gpio_set_value(gpio_nreset, 1);
        /* Give the codec time to wake up */
        udelay(1);
    }
 
    cs4271->gpio_nreset = gpio_nreset;
}
 
 
GPIO 입력 모드

아래는 GPIO를 입력 모드로 설정해서 사용하고 싶을 때의 예시 코드입니다. 특정 상황에서 GPIO PIN의 값을 읽고 싶을 경우 GPIO를 입력 모드로 쓰면 됩니다.
#define GPIO_NUMBER 11 
int gpio_nreset = GPIO_NUMBER;
static int cs4271_probe(struct snd_soc_codec *codec)
{
    struct cs4271_private *cs4271 = snd_soc_codec_get_drvdata(codec);
    struct cs4271_platform_data *cs4271plat = codec->dev->platform_data;
    int ret;
    int gpio_nreset = -EINVAL;

//GPIO 핀을 초기화
        if (gpio_request(gpio_nreset, "CS4271 Reset"))
            gpio_nreset = -EINVAL;
    if (gpio_nreset >= 0) {
        /* Reset codec */
    
// GPIO를 입력(input)모드로 변경
        gpio_direction_input(gpio_nreset);

// 나노 초만큼 딜레이를 줌
        udelay(1);

// GPIO 핀을 읽음 
        if(gpio_get_value(gpio_nreset) == 1) 
              printk(" Oh! GPIO PIN is one");
        /* Give the codec time to wake up */
        udelay(1);
    }
 
이번에 다른 예를 들어볼까요? 디바이스 드라이버 요구 사항을 먼저 알아볼까요?
 
     "만약에 리눅스 드라이버에서 커널 패닉이 발생한 경우 모뎀 프로세서에 GPIO로 이 사실을 알려주고
     (그래야 모뎀도 동작을 멈출 수 있겠죠.) 싶을 때는 GPIO를 어떻게 사용하면 될까?"
 
위 예시에서 언급한 모뎀은 AP 하드웨어와 떨어진 하드웨어를 의미합니다.
 
당연히 출력(output)모드로 사용하면 됩니다. 샘플 코드는 아래와 같아요.
(물론 위 시나리오에서 GPIO로 통신할 수 있는 구조로 회로가 설계되어 있다고 가정 하겠습니다.)

아래는 커널 패닉이 발생되었을 시에 모뎀 프로세서에 이를 알리는 부분인데, 어디선가 이 함수를 호출한다고 가정해요.
static int send_kernel_panic_msg_to_modem_processor(void)
{
    gpio_set_value(gpio_kernel_panic, 1);
}
 
아래는 GPIO를 초기화하는 코드에요.
#define GPIO_NUMBER_FOR_KERNEL_PANIC 12
int gpio_kernel_panic = GPIO_NUMBER_FOR_KERNEL_PANIC;
static int deliver_kernel_panic_accident_to_modem_probe(struct snd_soc_codec *codec)
{

    //GPIO 핀을 초기화
        if (gpio_request(gpio_kernel_panic, "Kernel Panic"))
            gpio_nreset = -EINVAL;

    // GPIO를 출력(아웃풋)모드로 변경하고 기본(default)로 0으로 설정, 
    // 그래야 나중에 1로 이 값을 올려야 정상동작이 가능함
        gpio_direction_output(gpio_kernel_panic, 0);
...
}
 
출력 모드/입력 모드 모두 사용하는 시나리오

두가지 GPIO모드를 사용하는 다른 시나리오를 만들어 볼까요? 유저 스페이스에서 debug fs로 어떤 값이 드라이버에 전달될 때, LCD를 제어하는 시나리오입니다.

     "/d/lcd_driver/val 노드에 1을 써주면 GPIO에 그대로 1이란 값을 써주자."
/d/lcd_driver/val 노드를 유저 스페이스에서 읽으려고 하면 GPIO 상태를 읽어 줘야하는 상황이에요.
#define GPIO_LCD_DRIVER_GET 15
#define GPIO_LCD_DRIVER_SET 16

int gpio_lcd_get = GPIO_LCD_DRIVER_GET;
int gpio_lcd_set = GPIO_LCD_DRIVER_SET; 

// /d/lcd_driver/val 노드를 유저 스페이스에서 읽으려고 하면 lcd_driver_get 함수가 호출되요.
// cat /d/lcd_driver/val
static int lcd_driver_get(void *data, u64 *val)
{
    int ret = 0;
    
    ret = gpio_get_value(gpio_lcd_get);    
    return ret;
}

// /d/lcd_driver/val 노드에 유저 스페이스에서 1로 설정을 하면 lcd_driver_set 함수가 호출이 됨
// echo /d/lcd_driver/val 1
static int lcd_driver_set(void *data, u64 val)
{
    if(val == 1) {
        gpio_set_value(gpio_lcd_set, 1);
    }
}
DEFINE_SIMPLE_ATTRIBUTE(lcd_driver_fops, lcd_driver_get, lcd_driver_set, "%llun");

static int lcd_driver_probe(struct platform_device *pdev)
{
    lcd_debug_debugfs_root = debugfs_create_dir("lcd_driver", NULL);
    debugfs_create_file("val", S_IRUGO, kernel_bsp_debug_debugfs_root, NULL, &lcd_driver_fops);

    //GPIO 핀을 초기화
    gpio_request(gpio_lcd_set, "lcd driver set");
    
    // GPIO를 출력(아웃풋)모드로 변경하고 기본(default)로 0으로 설정, 
    //그래야 나중에 1로 이 값을 올려야 정상동작이 가능함
    gpio_direction_output(gpio_lcd_set, 0);

    //GPIO 핀을 초기화
    gpio_request(gpio_lcd_get, "lcd driver set");

    // GPIO 핀을 입력 모드로 초기화    
    gpio_direction_input(gpio_lcd_get);
    return 0;
}
 
이 정도로 정리하면 잊어 먹지는 않을 것 같아요.
 
     "혹시 글을 읽고 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실하게 답변 올려 드리겠습니다!"
 
 
# Reference: For more information on 'Linux Kernel';
 
디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 1
 
디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 2
 
 
 
 

파워 관리 관점에서 안드로이드 디바이스는 간단히 두 가지 상태로 분류할 수 있다. 수면(Deep Sleep)과 깨어난(Wake)이다.

Sleep은 최소한의 파워 리소스를 사용하여 디바이스를 사용할 수 있는 상태이며, Wake는 프로세서 및 대부분의 파워 리소스가 사용을 하여 어떤 작업을 하고 있는 상태이다.  그런데, Wakelock은 디바이스별로 Sleep상태에서 깨우거나 Sleep상태로 들어가는 것을 막을 수 있는 기능이다.

PWL(Partial Wakelock)은 흔히 유저 스패이스에서 구동되고 있는 앱에서 일어나는 wakelock을 의미하며 일어나는 상황 역시 데이터 동기화, 영상 재생 중 화면 꺼짐 방지 등으로 다양하다. 앱을 설치할 때 퍼미션을 살펴보면 시스템 도구 항목 아래 절전 모드로 진입하는 것을 방지한다라고 명시된 부분이 wakelock을 뜻한다. 앱개발 시 WAKE_LOCK이란 퍼미션을 요청해야 하며 허락될 경우 필요한 때에 따라 Sleep상태로 들어가는 것을 방지할 수 있다.

가끔 오랜 시간동안 화면이 꺼져 있어도 디바이스가 Wake 상태였던 것을 종종 확인할 수 있다. 화면이 꺼져 있으며 유저가 디바이스를 사용하고 있지 않지만 wakelock을 통해 프로세서가 활성화되어 필요한 작업을 하고 있다는 것이다.  예를 들어, 특정 앱에서 작업을 스케줄 해둘 경우 시간에 맞춰 작업을 진행하고 끝날 때까진 Wake 상태를 유지한다. Sleep상태에선 최소한의 자원을 이용하지만 Wake 상태에선 파워 리소스를 사용해야 한다. 즉, 배터리를 더 소모한다는 것이다.

+ Recent posts