6. Flow of Time

Download Report

Transcript 6. Flow of Time

Chapter 6. Flow of Time
 커널의 타임 인터벌
 현재 시간 알기
 실행지연
 태스크 큐
 커널 타이머
순천향대학교 정보기술공학부
이상정
1
LINUX Device Drivers
단원 소개
 이 장에서는 커널에서 시간을 관리하는 방식을 소개
 커널 타이밍의 이해
 현재시간 알기
 지정된 시간 동안 오퍼레이션의 지연
 지정된 시간 경과 후 수행되는 비동기 함수들의 스케쥴링
순천향대학교 정보기술공학부
이상정
2
Ch.6 Flow of Time
커널의 타임 인터벌
(Time Intervals in the Kernel)
순천향대학교 정보기술공학부
이상정
3
LINUX Device Drivers
타이머 인터럽트
 커널은 시간을 추적하기 위해 일정 시간 간격으로 타이머 인
터럽트 이슈
• <linux/param.h>에 정의된 HZ 값에 따라 인터벌을 세팅
• 대부분의 플랫폼에서 HZ은 100으로 정의되지만 일부 플랫폼에서는
1024, IA-64에서는 20을 사용
• HZ은 주파수를 의미하므로 타이머 인터럽트가 초 당 HZ에 지정된
100번 발생함을 의미
• 따라서 타이머 인터럽트는 1/100초 , 10ms 마다 발생
 타이머 인터럽트가 발생할 때마다 변수 jiffies 가 증가
• jiffies는 <linux/sched.h>에 unsigned long volatile로 선언
• 시스템이 부팅될 때 jiffies는 0으로 초기화되어 컴퓨터가 켜진 이후
경과시간을 표시
• 오랜 동안 컴퓨터가 켜져있으면 오버플로우가 발생
• 대개 최대 16개월까지는 오버플로우가 발생하지 않음
순천향대학교 정보기술공학부
이상정
4
Ch.6 Flow of Time
LINUX Device Drivers
프로세서 카운터 레지스터
 좀 더 짧은 간격의 정밀한 시간 측정을 하려면 CPU가 제공
하는 하드웨어 카운터에 의존
• 현대 CPU는 매 클럭 사이클 마다 증가하는 카운터 제공
• 현대 프로세서에서는 명령 스케쥴링, 분기예측, 캐시 메모리 등 때문
에 명령의 타이밍을 예측하기가 어려워 이 카운터가 정밀한 시간 측
정을 위한 유일한 수단
• 카운터 레지스터는 플랫폼에 따라 다양
• x86 계열 펜티엄 프로세서에서는 64비트의 TSC (timestamp
counter) 카운터 레지스터 제공하고 커널과 사용자 영역 모두에서 읽
기 가능
순천향대학교 정보기술공학부
이상정
5
Ch.6 Flow of Time
LINUX Device Drivers
TSC 카운터 레지스터
 TSC 카운터 레지스터는 다음 매크로를 사용하여 읽음
#include <asm/msr.h> /* machine-specific registers */
rdtsc(low, high);
rdtscl(low);
• 전자는 두 개의 32비트 변수로 64비트의 값을 읽는 매크로
• 후자는 하위 32비트 값을 읽는 매크로
• 500MHz 시스템에서 매 8.5초 마다 32비트 카운터는 오버플로우
• 이들 매크로의 실행시간을 측정하는 예
unsigned long ini, end;
rdtscl(ini); rdtscl(end);
printk("time lapse: %li\n", end - ini);
순천향대학교 정보기술공학부
이상정
6
Ch.6 Flow of Time
LINUX Device Drivers
get_cycles() 함수
 커널은 rdtsc와 유사한 기능의 아키텍처 독립적인 함수인
get_cycles을 제공
#include <linux/timex.h>
cycles_t get_cycles(void);
• 사이클-카운트 레지스터가 없는 플랫폼에서는 0을 리턴
• cycles_t는 CPU 레지스터에 맞추어진 unsigned 타입으로 펜티엄인
경우 하위 32비트 값이 리턴
순천향대학교 정보기술공학부
이상정
7
Ch.6 Flow of Time
LINUX Device Drivers
인라인 어셈블리 코드
 인라인 어셈블리 코드(inline asssembly code)를 사용하여 카운터 레지
스터를 직접 읽을 수 있음
 x86과 같이 MIPS에서 rdtscl 함수를 구현 예
• MIPS에서는 내부 레지스터 9가 32비트 카운터
• 레지스터는 커널 영역에서만 읽을 수 있고 “move from coprocessor 0” 어
셈블리 명령을 실행하여 읽음
• 매크로를 다음과 같이 정의
#define rdtscl(dest) _ _asm_ _ _ _volatile_ _("mfc0 %0,$9; nop" : "=r" (dest))
• gcc 인라인 어셈블리는 범용 레지스터는 컴파일러가 할당
• %0 는 “argument 0”로 출력(=)의 임의의 레지스터(r)에 대응되고, C 표현의 dest
에 대응됨을 표시
• nop는 mfc 바로 다음의 명령이 타겟 레지스터를 액세스하지 못하게 하기 위해 삽
입
 앞의 C 코드 실행
• K7 계열의 x86 프로세서 실행하면 11 클럭 틱이 출력
• MIPS VR4181의 경우 2 클럭 틱이 출력
• MIPS는 RISC 계열 프로세서로 클럭 당 한 명령을 실행
순천향대학교 정보기술공학부
이상정
8
Ch.6 Flow of Time
현재 시간 알기
(Knowing the Current Time)
순천향대학교 정보기술공학부
이상정
9
LINUX Device Drivers
현재시간 측정
 드라이버는 jiffies의 값을 사용하여 이벤트 간의 시간 간격을
측정
• 예를들면 입력 디바이스 드라이버에서 더블 클릭과 싱글 클릭을 구분
하는 용도 등에 사용
• 아주 짧은 시간간격을 측정하기 위해서는 프로세서에 종속적인 레지
스터 값들을 이용
 드라이버가 현재시간을 알 필요가 있으면 do_gettimeofday
함수를 사용
• 이 함수는 gettimeofday 시스템 콜과 같이 초와 마이크로초 값을 갖
는 struct timeval 포인터를 채움
#include <linux/time.h>
void do_gettimeofday(struct timeval *tv);
• 현재시간은 struct timeval 형의 xtime 변수를 읽기로도 가능
• timeval 변수의 tv_sec, tv_usec 필드를 읽기 위해선 인터럽트를 금지
(disable)로 세팅해야 함
순천향대학교 정보기술공학부
이상정
10
Ch.6 Flow of Time
LINUX Device Drivers
jit-currenrtime
 jit(Just In Time) 샘플 코드
• 소스: source/ misc-modules Makefile jit.c
 jit 소스 코드
• 다음 세가지 형태의 ASCII 값을 리턴하는 /proc/currentime 을 생성
• do_gettimeofday 함수가 리턴한 현재시간
• xtime 변수의 현재시간
• 현재 jiffies의 값
• 단순히 3줄의 텍스트 라인을 출력하는 코드이므로 적은 모듈 코드로
프로그램 가능한 동적인 /proc 파일을 이용
• create_proc_read_entry("currentime",0, NULL, jit_read_current, NULL);
• 짧은 시간에 여러 번 /proc/currentime을 읽으면 xtime과
do_gettimeofday의 차이를 알 수 있는데 xtime의 갱신이 덜 빈번함을
알 수 있음
순천향대학교 정보기술공학부
이상정
11
Ch.6 Flow of Time
LINUX Device Drivers
jit-currenrtime 실행 예
# /sbin/insmod jit.o
# cat /proc/modules
jit
1828
scull
10124
parport_pc
19076
lp
8996
…………………
0 (unused)
0
1 (autoclean)
0 (autoclean)
# cd /proc; cat currentime currentime
gettime: 1104576850.664190
xtime: 1104576850.661441
jiffies: 81314167
gettime: 1104576850.664255
xtime: 1104576850.661441
jiffies: 81314167
# cat currentime currentime
gettime: 1104576853.635644
xtime: 1104576853.631441
jiffies: 81314464
gettime: 1104576853.635710
xtime: 1104576853.631441
jiffies: 81314464
순천향대학교 정보기술공학부
이상정
12
Ch.6 Flow of Time
실행지연
(Delaying Execution)
순천향대학교 정보기술공학부
이상정
13
LINUX Device Drivers
busy waiting 방식
 한 클럭 틱 보다 긴 지연은 시스템 클럭과 소프트웨어 루프로
구현
 busy waiting 방식
• 한 클럭 틱 이상의 긴 지연 시 사용
• 코드 구현
unsigned long j = jiffies + jit_delay * HZ;
while (jiffies < j)
/* nothing */;
• jiffies는 volatile로 선언되어 C 코드는 최적화를 수행하지 않음
• 인터럽트가 금지(disable)되지 말아야 함
– 타이머 인터럽트가 발생해야만 jiffies 값이 갱신되므로 인터럽트가 금지되면
무한 루프에 빠짐
순천향대학교 정보기술공학부
이상정
14
Ch.6 Flow of Time
LINUX Device Drivers
jit-busy
 jit(Just In Time) 샘플 코드
• 소스: source/ misc-modules Makefile jit.c
 jit 소스 코드
• 매 1초 마다 라인을 출력하는 /proc/jitbusy 생성
• read 메쏘드가 호출될 때마다 1초 동안 busy-loop을 수행
# cat /proc/jitbusy
=> 매 1초마다 라인 출력
This is a meaningless string, fiftysix characters long.
This is a meaningless string, fiftysix characters long.
This is a meaningless string, fiftysix characters long.
…………………
# dd if=/proc/jitbusy bs=1 => if에 기술된 파일을 매 1초마다 문자 출력
This is a meaningless string, fiftysix characters long.
This is a meaningless string, fiftysix characters long.
……………
순천향대학교 정보기술공학부
이상정
15
Ch.6 Flow of Time
LINUX Device Drivers
jit-sched
 proc/jitbusy를 읽는 것은 최악의 시스템 성능
• 컴퓨터가 1초당 한 번 다른 프로세스를 스케쥴하여 실행 가능
 다음 /proc/jitsched 같이 시간간격 동안에 다른 프로세스를 실행하는 것
이 더 좋은 방법
while (jiffies < j)
schedule();
• 이 방법은 경성 실시간 태스크(hard real-time tasks)나 다른 시간 의존적인
(time-critical) 상황에서는 사용할 수 없음
• j는 앞의 busy waiting에서 계산된 지연의 종료 시의 jiffies의 값
• 시스템은 다른 태스크들을 스케쥴
• 현재 프로세스는 아무 것도 하지 않고 CPU를 양도하지만 실행 큐(run queue)
에는 그대로 잔류
• 현재 프로세스가 유일하게 실행 가능한 프로세스(runnable process)라면 이
프로세스가 다시 실행(이는 다시 스케쥴러를 호출하고, 같은 프로세스를 선
택하고, 다시 스케쥴러를 호출하고,……)
• 일단 한 프로세스가 schedule로 프로세서를 양도하면 곧 다시 프로세서를 점
유한다는 보장은 없음
• 허용되는 지연 시간의 상한 값이 있는 경우 이 방식으로 schedule을 호출하는 것
은 바람직 하지 않음
순천향대학교 정보기술공학부
이상정
16
Ch.6 Flow of Time
LINUX Device Drivers
jit-queue, jit-self
 5장에서 소개된 수면함수의 타임아웃 버전을 사용
sleep_on_timeout(wait_queue_head_t *q, unsigned long timeout);
interruptible_sleep_on_timeout(wait_queue_head_t *q, unsigned long timeout);
• 이 두 함수는 주어진 대기 큐 상에서 수면을 하지만 어떤 경우에도 타임아웃
(jiffies 로 표현) 내에 리턴
 /proc/jitqueue는 다음과 같이 지연을 구현
wait_queue_head_t wait;
init_waitqueue_head (&wait);
interruptible_sleep_on_timeout(&wait, jit_delay*HZ);
• 이 구현에서는 누구도 대기 큐 상에 wake_up을 호출하지 않기 때문에 프로세
스는 항상 타임아웃이 종료되면 깨어남
 드라이버가 관심을 갖는 이벤트가 없다면 schedule_timeout 으로 직접 지
연을 구현(/proc/jitself)
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout (jit_delay*HZ);
• schedule_timeout은 주어진 시간까지 수면
순천향대학교 정보기술공학부
이상정
17
Ch.6 Flow of Time
LINUX Device Drivers
짧은 지연(short delays)
 하드웨어와 동기를 위해 아주 짧은 지연을 하는 경우 jiffies를 사용할 수
없음
 짧은 지연을 위해 udleay와 mdelay 커널함수 제공
#include <linux/delay.h>
void udelay(unsigned long usecs);
void mdelay(unsigned long msecs);
• 전자는 원하는 마이크로초 동안 실행을 지연하기 위해 소프트웨어 루프를 사
용
• 후자는 udelay 주변의 루프를 사용, udelay는 BogoMips 값을 이용
• 부팅 시 실행되는 BogoMips 계산의 결과인 loops_per_second 정수값에 기반하
여 루프가 수행
• loops_per_second 정수값은 8비트로 표현되기 때문에 udelay 호출은 짧은시간
지연을 위해 호출
• udelay를 위한 최대 값은 1000 마이크로초(1 밀리초)가 적당
• udelay와 mdelay는 busy-waiting 함수이므로 지연 동안 다른 태스크를 실행
할 수 없으므로 불가피한 경우에만 이들 함수를 사용
순천향대학교 정보기술공학부
이상정
18
Ch.6 Flow of Time
태스크 큐 (Task Queues)
순천향대학교 정보기술공학부
이상정
19
LINUX Device Drivers
태스크 큐 소개
 많은 드라이버에서 인터럽트가 아닌 방식으로 나중에 특정
태스크를 실행하는 능력이 요구
 커널은 나중에 실행될 태스크를 저장하는 태스크 큐(task
queue)를 지원
• 나중에 실행되는 태스크 등록
• 드라이버는 각기 자신의 태스크 큐를 선언하여 임의로 트리거
• 커널에 의해 실행(트리거)되는 미리 정의된 큐에 태스크를 등록
 태스크 큐는 호출할 함수들의 연결 리스트
• 함수의 포인터와 인수로 표시되는 태스크들로 구성
• 태스크가 실행될 때 하나의 void * 인수를 받아들이고 void를 리턴
• 포인터 인수는 루틴에 전달되는 자료구조
순천향대학교 정보기술공학부
이상정
20
Ch.6 Flow of Time
LINUX Device Drivers
태스크 큐 자료구조
#include <linux/tqueue.h>
struct tq_struct {
struct tq_struct *next;
int sync;
void (*routine)(void *);
void *data;
};
/* linked list of active bh’s */
/* must be initialized to zero */
/* function to call */
/* argument to function */
• next는 bh(bottom half)의 연결 리스트
• “half of an interrupt handler”를 의미(9장 소개)
• 하드웨어 인터럽트에서 처리하기는 너무 큰 비동기 태스크를 디바이스
드라이버가 처리하기 위해 제공되는 메커니즘
• 나중에 실행될 태스크는 routine과 data 필드에 지정
• sync 플래그는 같은 태스크를 하나 이상 큐에 저장하여 next 포인터
를 오염시키는 것을 방지하기 위해 사용
• 태스크가 큐에 일단 저장되면 구조체는 커널에 의해 소유되는 것으로
간주하고 태스크가 실행되기 전에는 수정할 수 없음
순천향대학교 정보기술공학부
이상정
21
Ch.6 Flow of Time
LINUX Device Drivers
태스크 큐 함수
 DECLARE_TASK_QUEUE(name);
• 이 매크로는 주어진 name의 태스크 큐를 선언하고 빈 상태로 초기화
 int queue_task(struct tq_struct *task, task_queue *list);
• 태스크를 큐에 저장
• 태스크가 이미 큐에 있으면 0을 리턴하고, 아니면 0이 아닌 값을 리
턴
• task_queue는 struct tq_struct의 포인터
 void run_task_quque(task_queue *list);
• 누적된 태스크들의 큐를 소모(실행)하는 함수
• 자신의 고유 큐를 선언하고 관리하는 경우가 아니라면 이 함수를 호
출할 필요가 없음
순천향대학교 정보기술공학부
이상정
22
Ch.6 Flow of Time
LINUX Device Drivers
태스크 큐 실행
 run_task_queue로 주어진 큐를 실행을 요청하면 리스트의
각 엔트리가 실행
• 태스크 큐로 작업하는 함수는 커널이 run_task_queue 호출할 때를
염두에 두어야 함
• 미리 정의된 태스크 큐들은 커널이 다른 작업을 하지 않을 때 실행
• 태스크를 큐에 저장한 프로세스가 실행될 때에는 이들은 실행되지 않음
– 이 프로세스가 수면 중이거나 실행이 종료될 때 태스크 실행
• 큐에 저장된 태스크들은 서로 독립적으로 순서에 관계없이 수행
• 태스크 큐의 또 다른 특징은 태스크가 실행이 종료된 후 다시 이전의
큐에 재스케쥴 가능
• 이러한 비동기적인 실행은 9장에서 소개되는 하드웨어 인터럽트와
유사
• 태스크 큐는 종종 소프트웨어 인터럽트의 결과로 실행
순천향대학교 정보기술공학부
이상정
23
Ch.6 Flow of Time
LINUX Device Drivers
인터럽트 모드 실행
 태스크 큐가 인터럽트 모드(또는 인터럽트 시간)에서 실행될
때 코드에 많은 제약
• 프로세스 컨텍스트가 없기 때문에 임의의 특정 프로세스와 관련된 사
용자 영역에로 접근이 허용도지 않음
• 인터럽트 모드에서는 current 포인터가 유효하지 않으며 사용될 수
없음
• 인터럽트 모드 코드는 schedule이나 sleep_on을 호출할 수 없다. 또
한 수면에 들어갈 수 있는 함수들도 호출할 수 없음
• 예를들어 kmalloc(…, GFP_KERNEL)을 호출하는 것은 규칙에 어긋남
• 수면에 들어갈 있기 때문에 세마포도 사용할 수 없음
 커널 코드는 in_interrupt() 함수를 사용하여 인터럽트 모드에
서 수행되는지 여부를 테스트
• 이 함수는 인수가 없고 프로세서가 인터럽트 시간에서 실행되면 0이
아닌 값을 리턴
순천향대학교 정보기술공학부
이상정
24
Ch.6 Flow of Time
LINUX Device Drivers
미리 정의된 태스크 큐
 커널이 미리 정의하여 관리하는 큐로 드라이버는 3개만 사용
• 이들 큐들은 <linux/tqueue.h> 에 선언
 스케쥴러 큐
• 스케쥴러 큐는 유일하게 프로세스의 컨텍스트 상에서 실행
• 따라서 실행되는 태스크의 제약에서 다소 자유로움
• 리눅스 2.4에서 이들 큐는 keventd 라 불리는 전용 커널 쓰레드로부터 실행
되고 schedule_task 함수를 경유하여 접근
• 구 버전에서는 keventd가 사용되지 않고 tq_scheduler 큐가 직접 관리
 tq_timer
• 타이머 틱이 이 큐를 실행
• 틱(함수 do_timer)은 인터럽트 시간에 실행되기 때문에 이 큐 안에 있는 태스
크들 역시 인터럽트 시간에서 실행
 tq_immediate
• 이미디어트 큐는 시스템 콜로부터 리턴되거나 스케쥴러가 실행될 때 가능한
한 빨리 실행
• 인터럽트 시간에서 실행
순천향대학교 정보기술공학부
이상정
25
Ch.6 Flow of Time
LINUX Device Drivers
 그림 6-1은 태스크 큐
를 사용하는 드라이버
의 시간적인 순서를 보
여주는 그림
 인터럽트 핸들러에서
tq_immediate에 함수
를 큐에 저장하는 드라
이버
순천향대학교 정보기술공학부
이상정
26
Ch.6 Flow of Time
LINUX Device Drivers
스케쥴러 큐
 스케쥴러 큐는 가장 손쉽게 사용할 수 있는 큐
• 인터럽트 모드에서 동작하지 않아서 많은 일을 할 수 있고,특히 수면
에 들어갈 수도 있음
• 큐에 태스크를 저장하기 위해 schedule_task를 호출
int schedule_task(struct tq_struct *task);
• task는 스케쥴될 태스크이고 태스크가 이미 큐에 없는 경우에는 0이 아
닌 값을 리턴
• 2.4 커널에서는 스케쥴러 큐의 태스크를 실행하는 특별한 프로세스
keventd를 커널이 실행
–
–
–
–
keventd는 태스크를 실행하기 위한 예측 가능한 프로세스 컨텍스트를 제공
이전 구현에서는 임의의 프로세스 컨텍스트 상에서 태스크를 실행
keventd 구현을 사용함으로써 큐의 태스크들이 수면에 들어갈 수 있게 됨
keventd가 수면에 들어가는 동안에는 스케쥴러 큐의 다른 태스크는 실행되
지 못하기 때문에 아주 짧은 시간 동안에만 태스크가 수면에 들어가야 함
• 일반적으로 스케쥴러 큐의 태스크들은 아주 짧게 실행
순천향대학교 정보기술공학부
이상정
27
Ch.6 Flow of Time
LINUX Device Drivers
jiq 예제
 jiq(“Just In Queue”) 모듈 예제 프로그램
• jit와 같이 dd나 다른 툴을 사용하여 읽을 수 있는 /proc 파일을 생성
• jiq를 읽는 프로세스는 버퍼가 채워질 때까지 수면에 들어감
• /proc 파일의 버퍼는 4KB 메모리의 페이지
• 이 수면은 다음과 같이 선언된 대기 큐가 관리
DECLARE_WAIT_QUEUE_HEAD (jiq_wait);
• 버퍼는 태스크 큐에 저장된 함수의 실행으로 채워짐
• 큐의 각 루틴은 버퍼에 문자열을 채움
• 각 문자열은 현재시간(jiffies), 이 루틴 동안의 current 프로세스 번호,
in_interrupt의 값을 표시
• 큐에 저장되는 루틴인 jiq_print_tq 함수에서 버퍼를 채움
• 큐에 채워지는 루틴은 다음과 같이 초기화
struct tq_struct jiq_task;
/* these lines are in jiq_init() */
jiq_task.routine = jiq_print_tq;
jiq_task.data = (void *)&jiq_data;
create_proc_read_entry("jiqsched", 0, NULL, jiq_read_sched, NULL);
순천향대학교 정보기술공학부
이상정
28
Ch.6 Flow of Time
LINUX Device Drivers
jiq-sched (1)
 /proc/jiqsched는 스케쥴러 큐를 사용하는 예제
• 다음의 읽기함수는 읽기동작을 하는 태스크를 스케쥴 큐에 저장하고 수면
• 태스크가 실행되어 버퍼가 채워지면 수면에서 깨어남
int jiq_read_sched(char *buf, char **start, off_t offset,
int len, int *eof, void *data)
{
jiq_data.len = 0;
jiq_data.buf = buf;
jiq_data.jiffies = jiffies;
/* nothing printed, yet */
/* print in this place */
/* initial time */
/* jiq_print will queue_task() again in jiq_data.queue */
jiq_data.queue = SCHEDULER_QUEUE;
schedule_task(&jiq_task);
/* ready to run */
interruptible_sleep_on(&jiq_wait); /* sleep till completion */
}
*eof = 1;
return jiq_data.len;
순천향대학교 정보기술공학부
이상정
29
Ch.6 Flow of Time
LINUX Device Drivers
jiq-sched (2)
/*
* Call jiq_print from a task queue
*/
void jiq_print_tq(void *ptr)
{
if (jiq_print (ptr)) {
struct clientdata *data = (struct clientdata *)ptr;
if (data->queue == SCHEDULER_QUEUE)
schedule_task(&jiq_task);
else if (data->queue)
queue_task(&jiq_task, data->queue);
if (data->queue == &tq_immediate)
mark_bh(IMMEDIATE_BH); /* this one needs to be marked */
}
}
순천향대학교 정보기술공학부
이상정
30
Ch.6 Flow of Time
LINUX Device Drivers
jiq-sched (3)
int jiq_print(void *ptr)
{
struct clientdata *data = (struct clientdata *)ptr;
int len = data->len;
char *buf = data->buf;
unsigned long j = jiffies;
if (len > LIMIT) {
wake_up_interruptible(&jiq_wait);
return 0;
}
if (len == 0)
len = sprintf(buf," time delta interrupt pid cpu command\n");
else
len =0;
/* intr_count is only exported since 1.3.5, but 1.99.4 is needed anyways */
len += sprintf(buf+len,"%9li %3li
%i %5i %3i %s\n",j, j - data->jiffies,
in_interrupt (), current->pid, smp_processor_id (), current->comm);
data->len += len; data->buf += len; data->jiffies = j;
return 1;
}
순천향대학교 정보기술공학부
이상정
31
Ch.6 Flow of Time
LINUX Device Drivers
jiq-sched 실행
# /sbin/insmod jiq.o
# cat /proc/modules
jiq
2696 0 (unused)
parport_pc
19076 1 (autoclean)
………
# cat /proc/jiqsched
time
delta interrupt pid cpu command
114131483 0
0
2 0
keventd
114131483 0
0
2 0
keventd
……
• time 필드는 태스크가 수행될 때 jiffies 값, delta는 마지막으로 태스크가 실
행된 이 후의 jiffies의 변화를, interrupt는 in_interrupt 함수의 리턴값, pid
는 실행되는 프로세스의 ID, cpu는 사용된 CPU의 수(단일 CPUdls 경우에
는 항상 0), command는 현재 프로세스를 실행하게 한 명령을 표시
• 태스크는 항상 keventd 프로세스 하에서 실행됨을 알 수 있음
순천향대학교 정보기술공학부
이상정
32
Ch.6 Flow of Time
LINUX Device Drivers
타이머 큐(timer queue)
 타이머 큐는 인터럽트 모드에서 동작하고 직접 tq_timer 큐를 사용하고,
시스템 부하로 인한 지연을 제거하여 다음 클럭 틱에 큐의 실행을 보장
 다음은 /proc/jiqtimer 샘플 코드의 예로 queue_task 함수를 사용하여 큐
에 저장하고 실행할 준비하는 코드
int jiq_read_timer(char *buf, char **start, off_t offset, int len,
int *eof, void *data)
{
jiq_data.len = 0;
/* nothing printed, yet */
jiq_data.buf = buf;
/* print in this place */
jiq_data.jiffies = jiffies;
/* initial time */
jiq_data.queue = &tq_timer;
/* re-register yourself here */
queue_task(&jiq_task, &tq_timer);
/* ready to run */
interruptible_sleep_on(&jiq_wait); /* sleep till completion */
*eof = 1;
return jiq_data.len;
}
순천향대학교 정보기술공학부
이상정
33
Ch.6 Flow of Time
LINUX Device Drivers
jiq-timer 실행 결과
 head /proc/jiqtimer 의 실행 결과로 태스크의 실행과 임의의 프로세스
실행 사이에 정확히 하나의 타이머 클럭 틱의 시간간격을 유지
time
delta
45084845 1
45084846 1
45084847 1
45084848 1
45084849 1
45084850 1
45084851 1
45084852 1
45084853 1
45084854 1
순천향대학교 정보기술공학부
이상정
interrupt
1
1
1
1
1
1
1
1
1
1
34
pid cpu
8783 0
8783 0
8783 0
8783 0
8784 0
8758 1
8789 0
8758 1
8758 1
8758 1
command
cc1
cc1
cc1
cc1
as
cc1
cpp
cc1
cc1
cc1
Ch.6 Flow of Time
LINUX Device Drivers
이미디어트 큐(immediate queue)
 이미디어트 큐는 bottom-half 메커니즘을 경유하여 실행
• bottom half를 표시(marking)하여 실행의 필요성을 커널에 알림
• tq_immeduiate인 경우 mark_bh(IMMEDIATE_BH)를 호출하여 알림
• 태스크 큐에 태스크를 저장 등록한 후에 mark_bh를 호출해야 함
• 이미디어트 큐는 가장 빨리 실행되는 큐로 인터럽트 시간에 실행
• 큐는 스케쥴러나 또는 한 프로세스가 시스템 콜로부터 리턴하자마자
소비
 안전한 시간에 가능한 한 빨리 실행되어야 하는 태스크를 실
행하는데 사용
• 인터럽트 관리루틴 밖에서 실행되는 프로그램 코드의 엔트리 포인트
를 제공하여 인터럽트 핸들러를 위한 자원으로 활용
• jiq-immed 예에서는 설명을 위해 사용자의 태스크를 이미디어트 큐
에 등록하였지만, 이미디어트 큐에 사용자의 태스크를 등록하지 않음
순천향대학교 정보기술공학부
이상정
35
Ch.6 Flow of Time
LINUX Device Drivers
jiq-immed
create_proc_read_entry("jiqimmed", 0, NULL, jiq_read_immed, NULL);
int jiq_read_immed(char *buf, char **start, off_t offset,
int len, int *eof, void *data)
{
jiq_data.len = 0;
/* nothing printed, yet */
jiq_data.buf = buf;
/* print in this place */
jiq_data.jiffies = jiffies;
/* initial time */
jiq_data.queue = &tq_immediate; /* re-register yourself here */
queue_task(&jiq_task, &tq_immediate); /* ready to run */
mark_bh(IMMEDIATE_BH);
interruptible_sleep_on(&jiq_wait); /* sleep till completion */
*eof = 1;
return jiq_data.len;
}
순천향대학교 정보기술공학부
이상정
36
Ch.6 Flow of Time
LINUX Device Drivers
사용자 태스크
 미리 정의된 큐와는 달리 사용자 고유의 큐(custom queue)
는 커널에 의해 자동으로 실행되지 않으므로 큐를 유지 보수
하는 프로그래머가 실행을 지시
 다음은 고유의 큐를 프로그램 순서
• 먼저 큐를 선언하고 변수선언을 확장하는 매크로를 다음과 같이 파일
시작, 함수들 밖에 기술
DECLARE_TASK_QUEUE(tq_custom);
• 큐를 선언한 후에 큐에 태스크를 등록
queue_task(&custom_task, &tq_custom);
• 큐에 누적된 태스크들을 실행
run_task_queue(&tq_custom);
순천향대학교 정보기술공학부
이상정
37
Ch.6 Flow of Time
LINUX Device Drivers
태스크릿(tasklets) (1)
 2.4 커널이 발표되기 직전에 커널 태스크들을 지연하는 새로
운 메커니즘인 태스크릿(tasklet)이 추가
• 현재 태스크릿은 bottom-half 태스크들을 실행하는 주요 메커니즘으
로 사용
• 태스크릿은 태스크 큐와 같이 안전한 시간까지 태스크를 지연하고 항
상 인터럽트 시간에 실행
• 태스크 큐와 같이 비록 여러 번 스케쥴될수 있지만 기본적으로 한번
실행
• 태스크릿들은 SMP 머신 상에서 병렬로 실행되고 처음에 스케쥴된
CPU 상에서의 다시 실행을 보장하여 더 우수한 캐시 동작을 하여 더
좋은 성능을 보장
순천향대학교 정보기술공학부
이상정
38
Ch.6 Flow of Time
LINUX Device Drivers
태스크릿(tasklets) (2)
 태스크릿을 지원하는 소프트웨어는 <linux/interrupt.h>의 부분이고 태스크릿 자체
는 다음 중 하나로 선언
DECLARE_TASKLET(name, function, data);
• 주어진 이름의 태스크릿을 선언
• 태스크릿이 실행될 때 주어진 데이터 값(unsigned long)을 갖는 주어진 함수
를 호출
DECLARE_TASKLET_DISABLED(name, function, data);
• 이 전과 같이 태스크릿을 선언하지만 처음 상태는 불능(disabled)으로 세팅
• 스케쥴은 되지만 가까운 장래에 가능(enabled)해질 때까지 실행되지 않음
 /proc/jiqtasklet 예
• 모듈은 태스크릿을 다음과 같이 선언
void jiq_print_tasklet (unsigned long);
DECLARE_TASKLET (jiq_tasklet, jiq_print_tasklet,
(unsigned long) &jiq_data);
• 드라이버가 실행될 태스크릿을 스케쥴하고자 할 때 tasklet_schedule을 호출
tasklet_schedule(&jiq_tasklet);
순천향대학교 정보기술공학부
이상정
39
Ch.6 Flow of Time
LINUX Device Drivers
jiq-tasklet
create_proc_read_entry("jiqtasklet", 0, NULL, jiq_read_tasklet, NULL);
int jiq_read_tasklet(char *buf, char **start, off_t offset, int len,
int *eof, void *data)
{
jiq_data.len = 0;
/* nothing printed, yet */
jiq_data.buf = buf;
/* print in this place */
jiq_data.jiffies = jiffies;
/* initial time */
tasklet_schedule(&jiq_tasklet);
interruptible_sleep_on(&jiq_wait);
/* sleep till completion */
*eof = 1;
return jiq_data.len;
}
void jiq_print_tasklet(unsigned long ptr)
{
if (jiq_print ((void *) ptr))
tasklet_schedule (&jiq_tasklet);
}
순천향대학교 정보기술공학부
이상정
40
Ch.6 Flow of Time
커널 타이머 (Kernel Timers)
순천향대학교 정보기술공학부
이상정
41
LINUX Device Drivers
커널 타이머
 커널 타이머는 미래의 특정 시간에 함수(타이머 핸들러)의
실행을 스케쥴하는데 사용
• 타이머는 함수가 호출될 시간을 명시하는 반면에 태스크는 정확히 실
행되는 시간을 알 수 없는 점에서 태스크 큐, 태스크릿과 다름
• 커널 타이머에 등록된 함수는 태스크 큐와 같이 오직 한 번만 실행
• 커널 타이머는 이중연결 리스트로 구성되어 여러 개 타이머 생성 가
능
 타이머는 프로세서가 시스템 콜을 수행하고 있어도 정확히
제 시간에 타이머를 만료하여 태스크를 실행하므로 레이스
조건을 유발
• 타이머 함수에서 사용되는 자료구조는 atomic types(10장에서 기술)
과 스핀락을 사용하여 병행접근(concurrent access)으로부터 보호
되어야 함
순천향대학교 정보기술공학부
이상정
42
Ch.6 Flow of Time
LINUX Device Drivers
타이머 자료구조
 다음은 <linux/timer.h>에 정의된 타이머의 자료구조
struct timer_list {
struct timer_list *next; /* never touch this */
struct timer_list *prev; /* never touch this */
unsigned long expires; /* the timeout, in jiffies */
unsigned long data;
/* argument to the handler */
void (*function)(unsigned long); /* handler of the timeout */
volatile int running; /* added in 2.4; don’t touch */
};
• 타이머의 타임아웃 값은 jiffies로 표현, jiffies의 값이 timer->expires
보다 크거나 같으면 timer->function이 실행
• 타임아웃은 절대값으로 표시되므로 현재의 jiffies의 값에 원하는 지연
값을 더해주어 설정
순천향대학교 정보기술공학부
이상정
43
Ch.6 Flow of Time
LINUX Device Drivers
타이머 관련 함수
 void init_timer(struct timer_list *timer);
• 타이머 구조체를 초기화
 void add_timer(struct timer_list *timer);
• 타이머를 타이머 리스트에 추가
 int mod_timer(struct timer_list *timer, unsigned long expires);
• 타이머의 만료 값을 변경
 int del_timer(struct timer_list *timer);
• 타이머가 만료되기 전에 타이머를 리스트에서 삭제
• 타이머가 만료된 경우에는 자동적으로 리스트에서 삭제됨
 int del_timer_sync(struct timer_list *timer);
• del_timer와 유사하나 어떤 CPU에서도 타이머 함수를 실행되지 않음을 보장
• 타이머 함수가 예상하지 못한 시간에 실행되고 있을 때 레이스 조건을 피하
기 위해 del_timer_sync 함수가 사용
• del_timer_sync 함수는 타이머 함수가 다시 자신을 추가하는 add_timer 함수
를 사용하지 않을 것임을 보장
순천향대학교 정보기술공학부
이상정
44
Ch.6 Flow of Time
LINUX Device Drivers
jitimer 예
 jiq 모듈의 /proc/jitimer
• 타이머를 사용하여 두 개의 라인을 출력하는 예로 앞의 태스크 큐와
같은 출력함수를 사용
• 첫번째 라인은 /proc/jitimer를 실행하는 사용자 프로세스가 호출한
read 콜로 생성
• 두번째 라인은 1초가 경과한 뒤 타이머 함수로부터 출력
 head /proc/jitimer의 출력
time
delta interrupt pid cpu command
45584582 0
0 8920 0
head
45584682 100
1
0 1
swapper
• 위 두 번째 라인 출력에서 타이머 함수는 인터럽트 모드에서 실행
순천향대학교 정보기술공학부
이상정
45
Ch.6 Flow of Time
LINUX Device Drivers
jitimer 소스 (1)
create_proc_read_entry("jitimer", 0, NULL, jiq_read_run_timer, NULL);
int jiq_read_run_timer(char *buf, char **start, off_t offset, int len, int
*eof, void *data)
{
jiq_data.len = 0;
/* prepare the argument for jiq_print() */
jiq_data.buf = buf;
jiq_data.jiffies = jiffies;
jiq_data.queue = NULL;
/* don't requeue */
init_timer(&jiq_timer);
/* init the timer structure */
jiq_timer.function = jiq_timedout;
jiq_timer.data = (unsigned long)&jiq_data;
jiq_timer.expires = jiffies + HZ;
/* one second */
순천향대학교 정보기술공학부
이상정
46
Ch.6 Flow of Time
LINUX Device Drivers
jitimer 소스 (2)
jiq_print(&jiq_data); /* print and go to sleep */
add_timer(&jiq_timer);
interruptible_sleep_on(&jiq_wait);
del_timer_sync(&jiq_timer); /* in case a signal woke us up */
*eof = 1;
return jiq_data.len;
}
void jiq_timedout(unsigned long ptr)
{
jiq_print((void *)ptr);
/* print 2nd line */
wake_up_interruptible(&jiq_wait); /* awake the process */
}
순천향대학교 정보기술공학부
이상정
47
Ch.6 Flow of Time
LINUX Device Drivers
과제
 실행, 결과 및 코드 분석
• 6장에서 소개된 소스 예 분석
• jit-currenttime jit-busy jit-sched jit-queue
• jiq-sched jiq-timer jiq-immed jiq-tasklet jitimer
순천향대학교 정보기술공학부
이상정
48
Ch.6 Flow of Time