커널코드는 타이머나 보톰하프 매커니즘을 사용하지 않고서도 실행을 일정시간 동안 지연할 수 있는 방법이 필요하다. 이것은 대개 주어진 작업을 하들웨어서 처리하기 위한 시간을 보유하기 위해 필요하다. 이러한 지연시간은 보통 매우 짧은데, 예를들면 네트워크 카드 표준에는 이더넷 모드 변경 시의 지연시간 2microsec로 기술하고 있다. 즉 드라이버에서 이더넷 속도를 설정하려면 최소한 2microsec이 필요하다.
커널은 지연의 의미에 따라 여러가지 해결책을 제공하고 있따.
바쁜 루프
아이디어는 간단하다. 즉 원하는 만큼의 클럭 틱이 지날 때까지 루프를 계속반복하는 것이다.
예를들면,
unsigned long delay = jiffies + 10;
while(time_before(jiffies, delay));
해당루프는jiffies가 delay보다 커질 때까지 계속되는데, 이는 겨우 10번의 클럭 틱이 발생하면 지나가는 시간이다.
마찬가지로,
unsigned long delay = jiffies + 2*HZ;
while(time_before(jiffies, delay)); /* 2초 *./
좀 더 나은 방법으로, 코드가 대기하는 동안 프로세서가 다른 작업을 하도록 허용하기 위해 현재 프로세스를 리스케줄링할 수 도 있다.
unsigned long delay = jiffies + 5 * HZ;
while(time_before(jiffies, delay))
cond_resched(); //cond_resched()를 호춣아면 새로운 프로세스르르 스케줄하지만, 이는 need_resched가 설정된 경우에만 그러하다. 이 방법은 프로세스 컨텍스트에서 사용하는 것이 좋은데, 왜냐하면 인터럽트 핸들러는 가능한 빠르게 실행돼야 하기 때문이다. 또한 어떠한 지연방법이든 락을 점유하거나 인터럽트를 비활성화한 채로 사용해서는 안된다.
짧은 지연
커널 코드는 아주 짧고 정확한 지연이 필요하다. 이 지연은 대개 하드웨어와의 동기를 마추기위해서 사용되는데, 그 지연 시간은 보통 1ms 미만이다. jiffies를 기반으로 하는 지연방법은 이런한 짧은 지연을 위해 사용할 수 없다.
다행히도 커널은 microsec과 ms단위의 지연을 위한 2개의 함수를 제공하고 있으며, 이것은 모두 <linux/delay.h>에 정의되어 있다. 이 함숟르은 jiffies를 사용하지 않는다.
void udelay(unsigned long usecs);
void mdelay(unsigned long msecs);
위의 함수는ㄴ 지정된 microsec(usec)동안, 아래쪽 함수는 지정된 ms동안 바쁜 루프를 돌면서 실행을 지연시킨다. 1초는 1000ms이고, 1ms는 1000usec에 해당한다.
udelay() 함수는 주어진 기간을 지연하려면 루프를 몇번 반복을 해야 하는가를 계산할 수 잇는 루프로 구현돼있다.
schedule_timeout()
실행을 지연하는 더 최적화된 방법은 schedule_timeout()을 사용하는 것이다. 이 함수는 지정된 시간이 경과할 떄까지는 태스크를 휴면시킨다. 이 방법은 휴면 기간이 지정된 시간과 정확히 같으리라는 보장은 없으며, 다만 그 기간이 적어도 지정된 값보다는 크다는 것만을 보장한다. 지정된 시간이 경과하면, 커널은 태스크를 깨운 후 다시 실행큐에 삽입한다.
set_current_state(TASK_INTERRUPTIBLE); //task의 상태를 인터럽트 가능한 휴면상태로 설정
schedule_timeout(s * HZ); //한숨자고 , "s"초 뒤에 깨어난다.
여기서 매개변수는 지피 단위로 된, 희망하는 상대적인 타임아웃 시간이다. 앞의 예제는 태스크를 s초 동안 인터럽트 가능한 휴면 상태에 둔다. 태스크가 TASK_INTERRUPTIBLE로 표기됐으므로 시그널을 받게되면 탕미아웃 전이라도 미리 깨어날 수 있다. schedule_timeout()을 호출하기 전에 태스크는 이 둘 중의 한가지 상태로 미리 설정돼 있어야 하며, 그렇지 않으면 절대로 휴면 상태로 바뀌지 않게 된다.
이 함수를 사용하는 코드는 반드시 프로세스 컨텍스트에 있어야 하며, 락을 보유해서는 안된다.
timeout 클럭 틱이 지나면 만료되는 타이머 timer를 생성한다. 또 타이머가 만료되었을 때 process_timeout()를 실행하도록 설정한다. 그 다음은 타이머를 활성하고, schedule() 를 호출한다. 태스크는 TASK_INTERRUPTIBLE이나 TASK_UNITTERRUPTIBLE로 표기돼있다라고 가정하므로, 스케줄러는 태스크를 실행하지 않고 ㅅ로운 태스크를 선택한다.
void process_timeout(unsigned long data)
{
wake_up_process((task_t *)data);
}
이 함수는 태스크를 TASk_RUNNING상태로 바꾸고 다시 실행큐에 삽입한다.
여기서 타임아웃 시간이 MAX_SCHEDULE_TIMEOUT인 경우, 태스크는 무기한 휴면하게 된다. 즉 이경우는 타이머가 설정되지 않으며, 따라서 바로 스케줄러를 호출한다. 만약 이러한 타입아웃 값을 사용했다면 해당 태스크를 깨우기 위해 다른 방법을 사용해야 할 것이다.
커널은 지연의 의미에 따라 여러가지 해결책을 제공하고 있따.
바쁜 루프
아이디어는 간단하다. 즉 원하는 만큼의 클럭 틱이 지날 때까지 루프를 계속반복하는 것이다.
예를들면,
unsigned long delay = jiffies + 10;
while(time_before(jiffies, delay));
해당루프는jiffies가 delay보다 커질 때까지 계속되는데, 이는 겨우 10번의 클럭 틱이 발생하면 지나가는 시간이다.
마찬가지로,
unsigned long delay = jiffies + 2*HZ;
while(time_before(jiffies, delay)); /* 2초 *./
좀 더 나은 방법으로, 코드가 대기하는 동안 프로세서가 다른 작업을 하도록 허용하기 위해 현재 프로세스를 리스케줄링할 수 도 있다.
unsigned long delay = jiffies + 5 * HZ;
while(time_before(jiffies, delay))
cond_resched(); //cond_resched()를 호춣아면 새로운 프로세스르르 스케줄하지만, 이는 need_resched가 설정된 경우에만 그러하다. 이 방법은 프로세스 컨텍스트에서 사용하는 것이 좋은데, 왜냐하면 인터럽트 핸들러는 가능한 빠르게 실행돼야 하기 때문이다. 또한 어떠한 지연방법이든 락을 점유하거나 인터럽트를 비활성화한 채로 사용해서는 안된다.
짧은 지연
커널 코드는 아주 짧고 정확한 지연이 필요하다. 이 지연은 대개 하드웨어와의 동기를 마추기위해서 사용되는데, 그 지연 시간은 보통 1ms 미만이다. jiffies를 기반으로 하는 지연방법은 이런한 짧은 지연을 위해 사용할 수 없다.
다행히도 커널은 microsec과 ms단위의 지연을 위한 2개의 함수를 제공하고 있으며, 이것은 모두 <linux/delay.h>에 정의되어 있다. 이 함숟르은 jiffies를 사용하지 않는다.
void udelay(unsigned long usecs);
void mdelay(unsigned long msecs);
위의 함수는ㄴ 지정된 microsec(usec)동안, 아래쪽 함수는 지정된 ms동안 바쁜 루프를 돌면서 실행을 지연시킨다. 1초는 1000ms이고, 1ms는 1000usec에 해당한다.
udelay() 함수는 주어진 기간을 지연하려면 루프를 몇번 반복을 해야 하는가를 계산할 수 잇는 루프로 구현돼있다.
schedule_timeout()
실행을 지연하는 더 최적화된 방법은 schedule_timeout()을 사용하는 것이다. 이 함수는 지정된 시간이 경과할 떄까지는 태스크를 휴면시킨다. 이 방법은 휴면 기간이 지정된 시간과 정확히 같으리라는 보장은 없으며, 다만 그 기간이 적어도 지정된 값보다는 크다는 것만을 보장한다. 지정된 시간이 경과하면, 커널은 태스크를 깨운 후 다시 실행큐에 삽입한다.
set_current_state(TASK_INTERRUPTIBLE); //task의 상태를 인터럽트 가능한 휴면상태로 설정
schedule_timeout(s * HZ); //한숨자고 , "s"초 뒤에 깨어난다.
여기서 매개변수는 지피 단위로 된, 희망하는 상대적인 타임아웃 시간이다. 앞의 예제는 태스크를 s초 동안 인터럽트 가능한 휴면 상태에 둔다. 태스크가 TASK_INTERRUPTIBLE로 표기됐으므로 시그널을 받게되면 탕미아웃 전이라도 미리 깨어날 수 있다. schedule_timeout()을 호출하기 전에 태스크는 이 둘 중의 한가지 상태로 미리 설정돼 있어야 하며, 그렇지 않으면 절대로 휴면 상태로 바뀌지 않게 된다.
이 함수를 사용하는 코드는 반드시 프로세스 컨텍스트에 있어야 하며, 락을 보유해서는 안된다.
1434signed long __sched schedule_timeout(signed long timeout) 1435{ 1436 struct timer_list timer; 1437 unsigned long expire; 1438 1439 switch (timeout) 1440 { 1441 case MAX_SCHEDULE_TIMEOUT: 1442 /* 1443 * These two special cases are useful to be comfortable 1444 * in the caller. Nothing more. We could take 1445 * MAX_SCHEDULE_TIMEOUT from one of the negative value 1446 * but I' d like to return a valid offset (>=0) to allow 1447 * the caller to do everything it want with the retval. 1448 */ 1449 schedule(); 1450 goto out; 1451 default: 1452 /* 1453 * Another bit of PARANOID. Note that the retval will be 1454 * 0 since no piece of kernel is supposed to do a check 1455 * for a negative retval of schedule_timeout() (since it 1456 * should never happens anyway). You just have the printk() 1457 * that will tell you if something is gone wrong and where. 1458 */ 1459 if (timeout < 0) { 1460 printk(KERN_ERR "schedule_timeout: wrong timeout " 1461 "value %lx\n", timeout); 1462 dump_stack(); 1463 current->state = TASK_RUNNING; 1464 goto out; 1465 } 1466 } 1467 1468 expire = timeout + jiffies; 1469 1470 setup_timer_on_stack(&timer, process_timeout, (unsigned long)current); 1471 __mod_timer(&timer, expire, false, TIMER_NOT_PINNED); 1472 schedule(); 1473 del_singleshot_timer_sync(&timer); 1474 1475 /* Remove the timer from the object tracker */ 1476 destroy_timer_on_stack(&timer); 1477 1478 timeout = expire - jiffies; 1479 1480 out: 1481 return timeout < 0 ? 0 : timeout; //만료시간 이전에 깨어나면 중지된 시간을 리턴, 이벤트에 의해 깸. 1482}
timeout 클럭 틱이 지나면 만료되는 타이머 timer를 생성한다. 또 타이머가 만료되었을 때 process_timeout()를 실행하도록 설정한다. 그 다음은 타이머를 활성하고, schedule() 를 호출한다. 태스크는 TASK_INTERRUPTIBLE이나 TASK_UNITTERRUPTIBLE로 표기돼있다라고 가정하므로, 스케줄러는 태스크를 실행하지 않고 ㅅ로운 태스크를 선택한다.
void process_timeout(unsigned long data)
{
wake_up_process((task_t *)data);
}
이 함수는 태스크를 TASk_RUNNING상태로 바꾸고 다시 실행큐에 삽입한다.
여기서 타임아웃 시간이 MAX_SCHEDULE_TIMEOUT인 경우, 태스크는 무기한 휴면하게 된다. 즉 이경우는 타이머가 설정되지 않으며, 따라서 바로 스케줄러를 호출한다. 만약 이러한 타입아웃 값을 사용했다면 해당 태스크를 깨우기 위해 다른 방법을 사용해야 할 것이다.
'OS이야기' 카테고리의 다른 글
영역(Zone) (0) | 2011.10.18 |
---|---|
메모리 관리 (0) | 2011.10.17 |
타이머 (0) | 2011.10.17 |
현재시각(wall time) (0) | 2011.10.17 |
jiffies wraparound (0) | 2011.10.17 |