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