ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Linux 스레드
    카테고리 없음 2023. 9. 16. 04:22
    • 커널 시간 : 타이머 인터럽트 호출 횟수
      • 초당 정해진 빈도 : 1초 10번, 1000번
      • 단위 : HZ
      • 기능 : 
        • 주파수 높으면 시간 해상도 상승, 오버헤드 발생
        • 인터럽트 발생 횟수 : jiffies 카운터 (volitile 타입)
        • 시간 : jiffies / HZ
    • 시간의 비교
      • time_after(a, b) : a > b 
      • time_before(a, b) : a < b
      • time_after_eq : a >= b
      • time_before_eq : a <= b
    • busy waiting
      • 프로세스가 하나의 작업만 처리하는 대기 방식
    #include <linux/time.h>

    void wait_in_secs(int secs)
    {
       unsigned long delay; delay = jiffies + 3 * HZ;
       //delay < jiffies -> true 
       while(time_before(jiffies, delay));
    }
    • schedule
      • 비동기 방식
      • schedule_timeout : schedule함수는 호출한 프로세스로 언제돌아올지 모르기 때문에, 타임아웃을 지정해야한다.
    • ndelay, udelay, mdelay
      • 아주 작은 단위(nano, micro)의 실행을 지연해야한다면 sftware loop로 시간 지연
      • jiffy 1~10ms 단위로 증가한다. 따라서 작은 단위의 실행 지연 할 수 없다.
    • 커널 타이머 
      • 비동기적으로 커널에게 10초 뒤 작업을 실행해달라고 부탁하려면 타이머를 사용해야 한다.
      • 오차 : 타이머 인터럽트 발생할 때마다 처리되므로 타이머 인터럽트 주기 (1/HZ)초보다 정밀할 수 없다.
    • 타이머의 특징
      • 타이머는 자기 자신을 다시 등록할 수 있다.
        • callback 함수에서 다시 등록
        • 삭제 후 재 등록
    • 타이머는 등록한 CPU에서 실행된다. 
      • 멀티 코어 환경에서 스레드는 등록한 CPU에서만 실행된다는 뜻
    •  

    struct timer_list

    /* include/linux/timer.h
    struct timer_list {
    	/*
    	 * All fields that change during normal runtime grouped to the
    	 * same cacheline
    	 */
    	struct hlist_node	entry;
    	unsigned long		expires;
    	void			(*function)(struct timer_list *);
    	u32			flags;
    
    #ifdef CONFIG_LOCKDEP
    	struct lockdep_map	lockdep_map;
    #endif
    };

    expires는 타이머가 만료되는 시간으로, 보통 jiffies + @로 설정된다. 그 다음에 오는 function은 타이머가 만료되었을 때 실행되는 콜백 함수이다. flags는 타이머의 속성을 나타내는 플래그로, timer.h에 다음과 같이 설명되어있다. 

    /* include/linux/timer.h */
    
    /**
     * @TIMER_DEFERRABLE: A deferrable timer will work normally when the
     * system is busy, but will not cause a CPU to come out of idle just
     * to service it; instead, the timer will be serviced when the CPU
     * eventually wakes up with a subsequent non-deferrable timer.
     *
     * @TIMER_IRQSAFE: An irqsafe timer is executed with IRQ disabled and
     * it's safe to wait for the completion of the running instance from
     * IRQ handlers, for example, by calling del_timer_sync().
     *
     * Note: The irq disabled callback execution is a special case for
     * workqueue locking issues. It's not meant for executing random crap
     * with interrupts disabled. Abuse is monitored!
     *
     * @TIMER_PINNED: A pinned timer will not be affected by any timer
     * placement heuristics (like, NOHZ) and will always expire on the CPU
     * on which the timer was enqueued.
     *
     * Note: Because enqueuing of timers can migrate the timer from one
     * CPU to another, pinned timers are not guaranteed to stay on the
     * initialy selected CPU.  They move to the CPU on which the enqueue
     * function is invoked via mod_timer() or add_timer().  If the timer
     * should be placed on a particular CPU, then add_timer_on() has to be
     * used.
     */
    #define TIMER_CPUMASK		0x0003FFFF
    #define TIMER_MIGRATING		0x00040000
    #define TIMER_BASEMASK		(TIMER_CPUMASK | TIMER_MIGRATING)
    #define TIMER_DEFERRABLE	0x00080000
    #define TIMER_PINNED		0x00100000
    #define TIMER_IRQSAFE		0x00200000
    #define TIMER_INIT_FLAGS	(TIMER_DEFERRABLE | TIMER_PINNED | TIMER_IRQSAFE)
    #define TIMER_ARRAYSHIFT	22
    #define TIMER_ARRAYMASK		0xFFC00000

    근데 이상한 점이 있다. 보통 콜백함수에는 데이터를 인자로 넘겨준다. 근데 왜 데이터는 없고 timer_list*만 넘겨줄까? 궁금해서 찾아봤다 - Improving the kernel timers API (2017, lwn.net) 여기서 API를 바꾼 이유를 알려주고, stackoverflow 스레드에서 from_timer로 데이터를 가져오는 방법을 알려준다. (아래에서 다룰 예제에도 구현되어있다.)

    타이머의 초기화와 등록

    timer_setup

    /**
     * timer_setup - prepare a timer for first use
     * @timer: the timer in question
     * @callback: the function to call when timer expires
     * @flags: any TIMER_* flags
     *
     * Regular timer initialization should use either DEFINE_TIMER() above,
     * or timer_setup(). For timers on the stack, timer_setup_on_stack() must
     * be used and must be balanced with a call to destroy_timer_on_stack().
     */
    #define timer_setup(timer, callback, flags)			\
    	__init_timer((timer), (callback), (flags))
    
    #define timer_setup_on_stack(timer, callback, flags)		\
    	__init_timer_on_stack((timer), (callback), (flags))
    

    timer_list를 초기화하는 함수이다. timer_list, function, flags를 받는다.

    mod_timer

    타이머를 초기화했다고 끝난 건 아니다, 타이머를 커널에 등록해야 한다. 타이머 등록은 mod_timer 또는 add_timer로 수행된다. 주의할 점은 정확히 expires에 명시된 시간에 실행되지 않을 수도 있다는 것이다. 타이머는 정확하지 않을 수 있다. 하지만 최대한 expires 근처에서 실행하려고 노력할 것이다.

    add_timer

    /**
     * add_timer - start a timer
     * @timer: the timer to be added
     *
     * The kernel will do a ->function(@timer) callback from the
     * timer interrupt at the ->expires point in the future. The
     * current time is 'jiffies'.
     *
     * The timer's ->expires, ->function fields must be set prior calling this
     * function.
     *
     * Timers with an ->expires field in the past will be executed in the next
     * timer tick.
     */
    void add_timer(struct timer_list *timer)
    {
    	BUG_ON(timer_pending(timer));
    	__mod_timer(timer, timer->expires, MOD_TIMER_NOTPENDING);
    }
    EXPORT_SYMBOL(add_timer);

    add_timer는 현재 등록하는 타이머가 이미 등록되지 않은 경우에만 실행해야 하며, 호출 전에 반드시 expires, function 필드가 초기화되어있음을 보장해야 한다.

    /**
     * mod_timer - modify a timer's timeout
     * @timer: the timer to be modified
     * @expires: new timeout in jiffies
     *
     * mod_timer() is a more efficient way to update the expire field of an
     * active timer (if the timer is inactive it will be activated)
     *
     * mod_timer(timer, expires) is equivalent to:
     *
     *     del_timer(timer); timer->expires = expires; add_timer(timer);
     *
     * Note that if there are multiple unserialized concurrent users of the
     * same timer, then mod_timer() is the only safe way to modify the timeout,
     * since add_timer() cannot modify an already running timer.
     *
     * The function returns whether it has modified a pending timer or not.
     * (ie. mod_timer() of an inactive timer returns 0, mod_timer() of an
     * active timer returns 1.)
     */
    int mod_timer(struct timer_list *timer, unsigned long expires)
    {
    	return __mod_timer(timer, expires, 0);
    }
    EXPORT_SYMBOL(mod_timer);

    mod_timer는 이미 등록한 (하지만 아직 실행되지는 않은) 타이머의 시간을 고치는 함수이다. timer_list의 포인터, 새로 갱신할 expires (jiffies 기준)를 받는다. 주석을 보면 del_timer(timer); timer->expires = expires; add_timer(timer); 와 동일하다고 나와있다. 즉, 현재 타이머(timer_list)가 기존에 등록되었다면 제거하고 등록하고, 없으면 그냥 등록한다. 이 함수는 기존에 존재하지 않는 타이머를 등록할 때도 사용할 수 있다. mod_timer로 등록한 타이머가 기존에 존재하지 않았다면 0을 리턴하고, 기존에 존재하는 타이머의 시간을 수정했다면 1을 리턴한다. 

    타이머의 제거

    타이머는 보통 두 가지로 나뉜다. 하나는 미래에 해야 할 작업을 수행하는 타이머와, 나머지 하나는 어떤 작업에 대한 timeout을 알려주기 위한 타이머이다. 보통 전자의 경우에는 시간이 만료되어서 function을 호출해 작업을 수행하는 경우가 많다면, 후자의 경우에는 timeout이 지나기 전에 이벤트가 발생하면 timeout을 취소해야 한다. 이처럼 타이머를 종종 취소하는 경우가 있는데, 이때는 del_timer와 del_timer_sync를 사용한다.

    del_timer

    /**
     * del_timer - deactivate a timer.
     * @timer: the timer to be deactivated
     *
     * del_timer() deactivates a timer - this works on both active and inactive
     * timers.
     *
     * The function returns whether it has deactivated a pending timer or not.
     * (ie. del_timer() of an inactive timer returns 0, del_timer() of an
     * active timer returns 1.)
     */
    int del_timer(struct timer_list *timer)
    {
            struct timer_base *base;
            unsigned long flags;
            int ret = 0;
    
            debug_assert_init(timer);
    
            if (timer_pending(timer)) {
                    base = lock_timer_base(timer, &flags);
                    ret = detach_if_pending(timer, base, true);
                    raw_spin_unlock_irqrestore(&base->lock, flags);
            }
    
            return ret;
    }
    EXPORT_SYMBOL(del_timer);

    del_timer는 등록되었지만, 아직 실행되지는 않은 타이머를 제거한다. 이미 실행된 타이머는 알아서 제거되므로 이에 대해서는 del_timer를 호출할 필요가 없다.

    del_timer_sync

    #if defined(CONFIG_SMP) || defined(CONFIG_PREEMPT_RT)
    /**
     * del_timer_sync - deactivate a timer and wait for the handler to finish.
     * @timer: the timer to be deactivated
     *
     * This function only differs from del_timer() on SMP: besides deactivating
     * the timer it also makes sure the handler has finished executing on other
     * CPUs.
     *
     * Synchronization rules: Callers must prevent restarting of the timer,
     * otherwise this function is meaningless. It must not be called from
     * interrupt contexts unless the timer is an irqsafe one. The caller must
     * not hold locks which would prevent completion of the timer's
     * handler. The timer's handler must not call add_timer_on(). Upon exit the
     * timer is not queued and the handler is not running on any CPU.
     *
     * Note: For !irqsafe timers, you must not hold locks that are held in
     *   interrupt context while calling this function. Even if the lock has
     *   nothing to do with the timer in question.  Here's why::
     *
     *    CPU0                             CPU1
     *    ----                             ----
     *                                     <SOFTIRQ>
     *                                       call_timer_fn();
     *                                       base->running_timer = mytimer;
     *    spin_lock_irq(somelock);
     *                                     <IRQ>
     *                                        spin_lock(somelock);
     *    del_timer_sync(mytimer);
     *    while (base->running_timer == mytimer);
     *
     * Now del_timer_sync() will never return and never release somelock.
     * The interrupt on the other CPU is waiting to grab somelock but
     * it has interrupted the softirq that CPU0 is waiting to finish.
     *
     * The function returns whether it has deactivated a pending timer or not.
     */
    int del_timer_sync(struct timer_list *timer)
    

    del_timer_sync는 SMP 환경에서만 정의되며, 그 외에는 del_timer와 동일하다. del_timer_sync가 리턴된된 후에는 SMP 환경에서, 현재 프로세서가 아닌 다른 프로세서에서도 모두 종료되었음을 보장한다. (동기적으로 기다린다.)

    /**
     * del_timer - deactivate a timer.
     * @timer: the timer to be deactivated
     *
     * del_timer() deactivates a timer - this works on both active and inactive
     * timers.
     *
     * The function returns whether it has deactivated a pending timer or not.
     * (ie. del_timer() of an inactive timer returns 0, del_timer() of an
     * active timer returns 1.)
     */
    int del_timer(struct timer_list *timer)
    {
            struct timer_base *base;
            unsigned long flags;
            int ret = 0;
    
            debug_assert_init(timer);
    
            if (timer_pending(timer)) {
                    base = lock_timer_base(timer, &flags);
                    ret = detach_if_pending(timer, base, true);
                    raw_spin_unlock_irqrestore(&base->lock, flags);
            }
    
            return ret;
    }
    EXPORT_SYMBOL(del_timer);

    del_timer는 등록되었지만, 아직 실행되지는 않은 타이머를 제거한다. 이미 실행된 타이머는 알아서 제거되므로 이에 대해서는 del_timer를 호출할 필요가 없다.

    del_timer_sync

    #if defined(CONFIG_SMP) || defined(CONFIG_PREEMPT_RT)
    /**
     * del_timer_sync - deactivate a timer and wait for the handler to finish.
     * @timer: the timer to be deactivated
     *
     * This function only differs from del_timer() on SMP: besides deactivating
     * the timer it also makes sure the handler has finished executing on other
     * CPUs.
     *
     * Synchronization rules: Callers must prevent restarting of the timer,
     * otherwise this function is meaningless. It must not be called from
     * interrupt contexts unless the timer is an irqsafe one. The caller must
     * not hold locks which would prevent completion of the timer's
     * handler. The timer's handler must not call add_timer_on(). Upon exit the
     * timer is not queued and the handler is not running on any CPU.
     *
     * Note: For !irqsafe timers, you must not hold locks that are held in
     *   interrupt context while calling this function. Even if the lock has
     *   nothing to do with the timer in question.  Here's why::
     *
     *    CPU0                             CPU1
     *    ----                             ----
     *                                     <SOFTIRQ>
     *                                       call_timer_fn();
     *                                       base->running_timer = mytimer;
     *    spin_lock_irq(somelock);
     *                                     <IRQ>
     *                                        spin_lock(somelock);
     *    del_timer_sync(mytimer);
     *    while (base->running_timer == mytimer);
     *
     * Now del_timer_sync() will never return and never release somelock.
     * The interrupt on the other CPU is waiting to grab somelock but
     * it has interrupted the softirq that CPU0 is waiting to finish.
     *
     * The function returns whether it has deactivated a pending timer or not.
     */
    int del_timer_sync(struct timer_list *timer)
    

    del_timer_sync는 SMP 환경에서만 정의되며, 그 외에는 del_timer와 동일하다. del_timer_sync가 리턴된된 후에는 SMP 환경에서, 현재 프로세서가 아닌 다른 프로세서에서도 모두 종료되었음을 보장한다. (동기적으로 기다린다.)

    timer example

    간단하게 타이머를 이용해 1초마다 로그를 남기는 모듈 예제이다.

    타이머가 스스로를 다시 등록하기 때문에, race condition을 막기 위해서 spinlock을 사용했다.

    #include <linux/kernel.h>
    #include <linux/module.h>
    #include <linux/time.h>
    #include <linux/spinlock.h>
    
    #define MODULE_NAME "TIMER"
    #define DELAY (1 * HZ)
    
    struct timer_data {
            int value;
            spinlock_t lock;
            struct timer_list timer;
            bool isActive;
    };
    
    struct timer_data my_data = {};
    
    void timer_callback(struct timer_list *timer) {
            struct timer_data *data = from_timer(data, timer, timer);
    
            data->value++;
            printk(KERN_INFO "[%s] value is = %d\n", __func__, data->value);
            spin_lock(&data->lock);
            if (data->isActive)
                    mod_timer(timer, jiffies + DELAY);
            spin_unlock(&data->lock);
    }
    
    int __init timer_init(void) {
            printk("[%s] creating timer...\n", __func__);
    
            /* initialization */
            my_data.isActive = true;
            spin_lock_init(&my_data.lock);
            timer_setup(&my_data.timer, timer_callback, 0);
    
            /* register timer */
            mod_timer(&my_data.timer, jiffies + DELAY);
            return 0;
    }
    
    void __exit timer_exit(void) {
            int ret;
    
            spin_lock(&my_data.lock);
            my_data.isActive = false;
            ret = del_timer(&my_data.timer);
            spin_unlock(&my_data.lock);
    
            printk("[%s] deleting timer..., ret = %d\n", __func__, ret);
    }
    
    module_init(timer_init);
    module_exit(timer_exit);
    
    MODULE_LICENSE("GPL");
Designed by Tistory.