2011. 10. 17. 18:29
jiffies wraparound OS이야기2011. 10. 17. 18:29
unsigned long timeout = jiffies + HZ/2; //0.5후에 타임아웃
/* do something */
/*너무 오래 걸렸는지 확인 */
if( timeout < jiffies)
//타임아웃되지 않았다. 양호하다
else
//타임아웃되었따. 에러다
이 코드에서 오버플로우가 된다면, timeout < jiffies 문이 에러가 된것으로 처리하게 된다.
타이머 인터럽트 핸들러
타이머 인터럽트는 두부분 - 아키텍쳐 종속적인 부분과 아키텍쳐 독립적인 부분으로 나눈다.
아키텍쳐 종속적인 부분은 시슷템 타이머의 인터럽트 핸들러로 등록되며, 따라서 타이머 인터럽트가 발생할 때 실행된다.
정확한 작업 내역은 물론 아키텍쳐에 따라 다르지만 대부분의 핸들러는 다음과 같은 일을 수행한다.
- xtime_lock을 얻는다. 이 락은 jiffies_64에 대한 접근과 현재 시각 값인 xtime을 보호한다.
- 필요에 따라 시스템 타이머를 승인하거나 초기화한다.
- 주기적으로 현재 시각을 갱신하여 실시간 클럭에 저장한다.
- 이키텍쳐 독립적인 타이머 루틴 do_timer()를 호출한다.
아키텍쳐 독립적인 부분인 do_timer()는 좀더많은 작업을 수행한다.
- jiffies_64 수를 1만큼 증가시킨다.
- 현재 실행 중인 프로세스에 대해 시스템 및 유저 시간 소비량과 같은 자원 사용 현황을 갱신한다.
- 만료된 동적 타이머가 있다면 다시 실행한다.
- scheduler_tick()을 호출한다.
- xtime에 저장된 현재 시각을 갱신한다.
- 악명 높은 부하 평균(load average)를 계산한다.
실제함수는 다음고 ㅏ같이 간단한데 대부분이 작업이 다루 함ㅎ수에서 처리되기 때문이다.
void do_timer(struct pt_regs *regs)
{
jiffies_64++;
update_process_times(user_mode(regs));
update_times();
}
user_mode() 매크로는 프로세서 레지스터인 regs의 상태를 검사하여 유저 공간에서 타이머 인터럽트가 발생했으면 1을, 커널 모드에서 인터럽트가 발생했으면 0을 리턴한다. 이러한 식으로 update_process_times()로 하여금 이전의 타이머 틱이 우저와 커널 모드 중 어디에서 일어났는지 알 수 있도록 한다.
void update_process_times(int user_tick)
{
/* do something */
/*너무 오래 걸렸는지 확인 */
if( timeout < jiffies)
//타임아웃되지 않았다. 양호하다
else
//타임아웃되었따. 에러다
이 코드에서 오버플로우가 된다면, timeout < jiffies 문이 에러가 된것으로 처리하게 된다.
타이머 인터럽트 핸들러
타이머 인터럽트는 두부분 - 아키텍쳐 종속적인 부분과 아키텍쳐 독립적인 부분으로 나눈다.
아키텍쳐 종속적인 부분은 시슷템 타이머의 인터럽트 핸들러로 등록되며, 따라서 타이머 인터럽트가 발생할 때 실행된다.
정확한 작업 내역은 물론 아키텍쳐에 따라 다르지만 대부분의 핸들러는 다음과 같은 일을 수행한다.
- xtime_lock을 얻는다. 이 락은 jiffies_64에 대한 접근과 현재 시각 값인 xtime을 보호한다.
- 필요에 따라 시스템 타이머를 승인하거나 초기화한다.
- 주기적으로 현재 시각을 갱신하여 실시간 클럭에 저장한다.
- 이키텍쳐 독립적인 타이머 루틴 do_timer()를 호출한다.
아키텍쳐 독립적인 부분인 do_timer()는 좀더많은 작업을 수행한다.
- jiffies_64 수를 1만큼 증가시킨다.
- 현재 실행 중인 프로세스에 대해 시스템 및 유저 시간 소비량과 같은 자원 사용 현황을 갱신한다.
- 만료된 동적 타이머가 있다면 다시 실행한다.
- scheduler_tick()을 호출한다.
- xtime에 저장된 현재 시각을 갱신한다.
- 악명 높은 부하 평균(load average)를 계산한다.
실제함수는 다음고 ㅏ같이 간단한데 대부분이 작업이 다루 함ㅎ수에서 처리되기 때문이다.
void do_timer(struct pt_regs *regs)
{
jiffies_64++;
update_process_times(user_mode(regs));
update_times();
}
user_mode() 매크로는 프로세서 레지스터인 regs의 상태를 검사하여 유저 공간에서 타이머 인터럽트가 발생했으면 1을, 커널 모드에서 인터럽트가 발생했으면 0을 리턴한다. 이러한 식으로 update_process_times()로 하여금 이전의 타이머 틱이 우저와 커널 모드 중 어디에서 일어났는지 알 수 있도록 한다.
void update_process_times(int user_tick)
{
struct task_struct *p = current;
int cpu = smp_processor_id();
int system = user_tick ^ 1; //배타적연산을 통해 분기없이, 커널이나, 사용자 프로세스에 tick을 덧셈.
update_one_process(p, user_tick, system, cpu);
run_local_timers();
scheduler_tick(user_tick, system);
int cpu = smp_processor_id();
int system = user_tick ^ 1; //배타적연산을 통해 분기없이, 커널이나, 사용자 프로세스에 tick을 덧셈.
update_one_process(p, user_tick, system, cpu);
run_local_timers();
scheduler_tick(user_tick, system);
}
update_one_process()가 바로 실질적으로 프로세서 시간을 갱신하는 함수이다. 이 함수는 약간 복잡해 보이는데, XOR연산을 사용하여 어떻게 user_tick과 system 중 하나에 1을, 다른 하나에 0을 할당하는가를 중심으로 살펴보자. 이렇게 함으로써 updates_one_process() 함수는 분기를 사용하지 않고 단지 각 값을 해당 카운터에 더하는 것으로 작업을 마칠 수 있게 된다.
update_one_process(p, user, system, cpu)
{
update_one_process()가 바로 실질적으로 프로세서 시간을 갱신하는 함수이다. 이 함수는 약간 복잡해 보이는데, XOR연산을 사용하여 어떻게 user_tick과 system 중 하나에 1을, 다른 하나에 0을 할당하는가를 중심으로 살펴보자. 이렇게 함으로써 updates_one_process() 함수는 분기를 사용하지 않고 단지 각 값을 해당 카운터에 더하는 것으로 작업을 마칠 수 있게 된다.
update_one_process(p, user, system, cpu)
{
p->utime += user;
p->stime += system;
p->stime += system;
}
적절한 값을 1만큼 증가시키고, 다른 값은 그대로 유지한다. 즉 타이머 인터럽트의 발생시 커널은 프로세서의 모드에 상관없이, 이전 틱 동안에 프로세스가 실행됐다고 생각한다는 말이다. 하지만, 실제로는 이전 틱 동안 여러 프로세스가 커널 모드로 진입했ㅇ르 가능성이 있다. 다시 말하면, 이 프로세스가 이전 틱 동안 커널에서 실행된 유일한 프로세스가 아닐 수 있다는 것이다. 하지만, 이렇게 틱 단위로 시간을 나눠 프로세스를 관리하는 것은 유닉스의 전통이며, 훨씬 복잡한 계산을 하지 않는 한 이것이 커너르이 최선책이다. 이것은 또한 진동수를 높게 설정해야 하는 또 다른 이유이기도 하다.
run_local_timers() 함수는 softirq로 하ㅕㅇ금 만료된 타이머를 실행하도록 한다. 마지막으로 scheduler_tick() 함수는 현재 실행중인 프로세스의 타임슬라이스를 감소시키고, 필요하다면 need_scheduled를 설정한다.
update_process_times()가 리턴되면, do_timer()는 update_times()를 호출하여 현재 시각을 갱신한다.
void update_times(void)
{
적절한 값을 1만큼 증가시키고, 다른 값은 그대로 유지한다. 즉 타이머 인터럽트의 발생시 커널은 프로세서의 모드에 상관없이, 이전 틱 동안에 프로세스가 실행됐다고 생각한다는 말이다. 하지만, 실제로는 이전 틱 동안 여러 프로세스가 커널 모드로 진입했ㅇ르 가능성이 있다. 다시 말하면, 이 프로세스가 이전 틱 동안 커널에서 실행된 유일한 프로세스가 아닐 수 있다는 것이다. 하지만, 이렇게 틱 단위로 시간을 나눠 프로세스를 관리하는 것은 유닉스의 전통이며, 훨씬 복잡한 계산을 하지 않는 한 이것이 커너르이 최선책이다. 이것은 또한 진동수를 높게 설정해야 하는 또 다른 이유이기도 하다.
run_local_timers() 함수는 softirq로 하ㅕㅇ금 만료된 타이머를 실행하도록 한다. 마지막으로 scheduler_tick() 함수는 현재 실행중인 프로세스의 타임슬라이스를 감소시키고, 필요하다면 need_scheduled를 설정한다.
update_process_times()가 리턴되면, do_timer()는 update_times()를 호출하여 현재 시각을 갱신한다.
void update_times(void)
{
unsigned long tick;
ticks = jiffies - wall_jiffies;
if(ticks)
{
ticks = jiffies - wall_jiffies;
if(ticks)
{
wall_jiffies += ticks;
update_wall_time(ticks)
update_wall_time(ticks)
}
last_time_offset = 0;
calc_load(ticks);
last_time_offset = 0;
calc_load(ticks);
}
ticks는 지난번 갱신 이후의 틱 값의 변화를 나타냄. 일반적인 경우라면 이 값은 물론 1이다. 하지만 때로는 타이머 인터럽트가 실패하여 틱이 사라질 수 있다. 이 상황은 인터럽트가 오랜 동안 비활성화 되는 경우 발생할 수 있다.
한편, wall_jiffies 의 값은 ticks만큼 증가되므로 따라서 가장 최근 현재시각 갱신 때의 jifffies의 값과 같게 된다.
또한 update_wll_time(ticks)를 호출하여 현재 시각 값인 xtime을 갱신한다. 끝으로 calc_load()를 호출하여 부하 평균을 갱신하고 리턴한다.
do_timer() 함수는 원래의 아키텍쳐에 종속적인 인터럽트 핸들러로 리턴하며, 이 핸들러는 필요한 정리작업을 한 후 xtime_lock락을 해제하고 리턴한다.
이 모든 과정은 1/HZ초마다 일어난다. 즉, 1초에 1000번 발생한다.
ticks는 지난번 갱신 이후의 틱 값의 변화를 나타냄. 일반적인 경우라면 이 값은 물론 1이다. 하지만 때로는 타이머 인터럽트가 실패하여 틱이 사라질 수 있다. 이 상황은 인터럽트가 오랜 동안 비활성화 되는 경우 발생할 수 있다.
한편, wall_jiffies 의 값은 ticks만큼 증가되므로 따라서 가장 최근 현재시각 갱신 때의 jifffies의 값과 같게 된다.
또한 update_wll_time(ticks)를 호출하여 현재 시각 값인 xtime을 갱신한다. 끝으로 calc_load()를 호출하여 부하 평균을 갱신하고 리턴한다.
do_timer() 함수는 원래의 아키텍쳐에 종속적인 인터럽트 핸들러로 리턴하며, 이 핸들러는 필요한 정리작업을 한 후 xtime_lock락을 해제하고 리턴한다.
이 모든 과정은 1/HZ초마다 일어난다. 즉, 1초에 1000번 발생한다.
'OS이야기' 카테고리의 다른 글
타이머 (0) | 2011.10.17 |
---|---|
현재시각(wall time) (0) | 2011.10.17 |
지피 - jiffy (0) | 2011.10.17 |
어떤 Bottom Half를 사용해야 하는가? (0) | 2011.10.16 |
태스크릿의 사용 (0) | 2011.10.15 |