리눅스 커널의 이해(1) : 커널의 일반적인 역할과 동작

저자: 서민우
출처: Embedded World

본 기사는 리눅스 커널 2.6이 hardware interrupt와 system call을 중심으로 어떻게 설계되었고, 구현 되었는지 살펴본다. 이 과정에서 리눅스 커널 2.6에 새로이 추가된 커널 preemption 기능을 자세히 살펴보기로 한다. 또한 커널의 동기화 문제와 이에 대한 해결책 등을 커널 source 내에서 찾아보기로 하고, 후에 device driver등을 작성할 때 이러한 해결책을 어떻게 적용할 수 있을지도 생각해 본다. 다음으로 리눅스 커널 2.6에 추가된 O(1) scheduler를 소스 수준에서 자세히 살펴 보기로 한다. 또한 task queue의 변형된 형태인 work queue의 사용법을 알아보기로 한다. 마지막으로 리눅스 커널 2.6에서는 어떻게 device driver를 작성해야 할지 구체적인 예를 보기로 한다.

이번 기사에서는 리눅스 커널을 소스 수준에서 구체적으로 들여다 보기 전에 일반적인 커널의 동작을 살펴보고, 이를 바탕으로 리눅스 커널의 전체적인 동작을 살펴보기로 한다.

1. 일반적인 커널의 동작

여기서는 process와 device 사이에서 커널이 수행해야 할 구체적인 역할을 몇 가지 살펴보고, 이를 기본으로 해서 일반적인 커널의 동작을 이해해 보기로 한다.

system call에 의한 커널의 구체적인 동작 1

일반적으로 process는 system call을 통해 커널에게 device로부터 data를 읽기를 요청한다. 그러면 커널은 device로부터 data를 읽기를 요청하고 현재 수행중인 process를 잠시 멈추기 위해 wait queue에 넣는다. 왜냐하면 device로부터 data가 도착해야 그 process를 다시 진행할 수 있기 때문이다(wait, sleep, block과 같은 용어는 이러한 상황에서 쓰인다). 그리고 새로운 process를 적절한 기준에 의해 선택해 수행하기 시작한다. 새로운 process를 선택하고 그 process로 전환하는 과정을 process scheduling이라고 한다. 그 이후에 몇 번의 process scheduling이 더 있을 수 있다.

이 과정을 processor의 관점에서 다시 보자.
processor가 process의 사용자 영역(응용프로그램 영역)을 수행하는 중에 system call 명령을 만나면 커널 영역으로 뛰어 들어간다. 커널 영역에는 device로부터 data를 읽기를 요청하고 현재 수행중인 process를 잠시 멈추기 위해 wait queue에 넣고 새로운 process를 선택해 수행하는 일련의 명령들이 있다(이러한 일련의 명령들을 process 또는 커널이 수행할 작업이라고 하자). 이러한 명령들에 따라 결국 processor는 새로운 process를 선택해 수행하기 시작한다. 그 이후에 몇 번의 process scheduling이 더 있을 수 있으며, processor는 임의의 시간에 임의의 process를 수행하고 있다.

hardware interrupt에 의한 커널의 구체적인 동작

processor가 임의의 process를 수행하는 동안에 device에는 data가 도착한다. device는 data의 도착을 물리적인 신호를 통해서 processor에게 알린다(이를 우리는 hardware interrupt라고 한다). 그러면 processor는 이 신호를 감지하고 커널 영역으로 뛰어 들어간다. 커널 영역에는 device에 도착한 data를 메모리로 읽어 오고, 그 data를 사용할 process에 맞게 적절하게 형태를 바꾸어, data를 기다리는 process에게 전달하고, 그 process를 wait queue에서 꺼내 ready queue로 집어넣은 후, ready queue로 들어간 process의 우선순위가 현재 수행 중이던 process의 우선순위보다 클 경우 process scheduling을 요청한 후 process scheduling을 수행하는 일련의 명령들이 있다. 이러한 명령들에 따라 결국 processor는 device로부터 data를 읽기를 요청한 process를 다시 선택해 수행하기 시작한다. processor는 다시 시작한 process의 커널 영역에서 사용자 영역으로 빠져 나가 사용자 영역을 계속해서 수행한다.

[그림 1]을 보면서 좀 더 구체적으로 이해해 보자. process P1은 사용자 영역의 A 부분에서 system call을 통해 커널 영역으로 들어간다. 커널 영역의 B 부분에서 device로부터 data를 읽기를 요청한 후 현재 수행중인 process를 wait queue에 넣는다. 그리고 C 부분에서 process scheduling을 수행한다. 이 부분을 좀 더 자세히 들여다보면 C 부분에서 시작한 process scheduling은 D 부분에서 끝나지 않고 process P2의 E 부분에서 끝난다. 즉, c 지점으로 들어가서 e 지점으로 나온다.

process scheduling

여기서 process scheduling의 동작을 좀 더 구체적으로 살펴보자. process scheduling은 크게 두 동작으로 나뉜다. 처음 동작은 새로 수행할 process를 선택하는 부분이다. 두번째 동작은 현재 수행하고 있는 process의 상태를 저장한 다음 새로 수행할 process의 상태를 복구하는 것이다. 이 동작을 우리는 문맥 전환이라고 한다. processor는 내부에 여러 개의 register를 가지고 있으며, 이 register를 이용해 process를 수행해 나간다. register는 memory와 같이 data를 저장하는 기능을 하지만, 접근 속도가 memory보다 빠르다. 따라서 비용상 그 개수가 많지는 않다. register는 processor architecture에 따라 R0, R1, ... 또는 EAX, EBX, ... 등의 이름을 가지며, 32 bit RISC processor의 경우 일반적으로 32 bit의 크기를 갖는다. processor는 register와 memory 또는 I/O device내의 register간에
data를 옮겨가면서 procss를 수행해 나간다. 따라서 process를 수행해 나감에 따라 register의 내용은 계속 바뀌게 된다. 문맥 전환 부분을 좀 더 자세히 들여다 보면 processor가 현재 process를 수행해 나가다 어느 순간에 register의 내용을 그대로 메모리에 저장한다. 새로 수행할 process의 경우도 현재 process처럼 이전에 저장한 register의 내용이 메모리에 있으며, 따라서 그 메모리에 저장한 register의 내용을 다시 processor의 register로 복구 시킨다. 그리고 새로운 process를 계속 수행해 나간다.

문맥 전환(context switching)

문맥 전환 부분을 좀 더 구체적으로 이해하기 위해 process scheduling의 동작을 다음과 같이 가정해 보자. 처음 동작에서 새로 수행할 process를 뽑았는데 그 process가 현재 수행하고 있던 process였다. 그러면 두 번째 동작은 다음과 같이 될 것이다. processor가 현재 process의 register의 내용을 메모리에 저장한다. 그리고 방금 전에 메모리에 저장한 register의 내용을 다시 processor의 register로 복구 시킨다. 그리고 현재 process를 계속 수행한다. 이럴 경우 [그림 1]에서 C 부분에서 시작한 process scheduling은 D 부분에서 끝나며, 논리적으로 process scheduling을 수행하지 않은 것과 같다. process scheduling의 본래 목적은 process간의 전환이며 따라서 현재 process와 새로 수행할 process가 있어야 그 본래 기능을 수행할 수 있다. 여기서는 문맥 전환의 동작을 이해하기 위하여 이와 같은 가정을 한 것이다.



[그림 1] system call과 hardware interrupt에 의한 커널의 구체적인 동작


그러면 process scheduling의 본래 기능으로 다시 돌아가 문맥 전환을 생각해 보자.
[그림 1]에서 현재 process를 P1, 새로 수행할 process를 P2라 하자. 그러면 process P1의 C 부분에서 시작한 process scheduling이 논리적으로 D 부분에서 끝나야 하는 것처럼(여기서는 실제로 J 부분에서 끝난다) 이전에 process P2의 F 부분에서 시작한 process scheduling은 논리적으로 E 부분에서 끝나는 것이다. 그러나 시간상으로는 process P1의 C 부분에서 시작한 process scheduling은 process P2의 E 부분에서 끝난다. 즉, c 지점으로 들어가서 e 지점으로 나온다.

이후에 process P2에서 process P3로(f에서 g로), process P3에서 process P4로, 몇 번의 process scheduling이 더 있을 수 있으며(h에서 … i로), 어느 순간 임의의 process Pn이 수행 중일 수 있다. [그림 1]에서 process Pn을 수행하는 중에 G 부분에서, process P1의 B 부분에서 data를 읽기를 요청한 device로부터, hardware interrupt가 들어올 수 있다. 그러면 process Pn은 G 부분에서 커널 영역으로 들어간다. 커널은 H 부분에서 device에 도착한 data를 메모리로 읽어 오고, 그 data를 사용할 process P1에 맞게 적절히 형태를 바꾸어 process P1에게 전달하고, process P1을 wait queue에서 꺼내 ready queue로 넣은 후, 새로이 ready queue로 들어간 process P1의 우선순위가 현재 수행 중인 process Pn의 우선순위보다 클 경우 process scheduling을 요청한다. 그러면 I 부분에서 process scheduling을 수행한다. process Pn의 I 부분에서 시작한 process scheduling은 process P1의 J 부분에서 끝난다. 덧붙이자면, process P1의 C 부분과 J 부분은 시간적으로는 연속이지 않지만 논리적으로는 연속이다.

hardware interrupt에 의한 커널의 일반적인 동작

이제 hardware interrupt에 의해 시작한 커널의 일반적인 동작을 정리해 보자.
[그림 1]에서 process Pn을 수행하는 중에 들어온 hardware interrupt에 의해 시작한 커널의 동작은 다음과 같다.

   1. device에 도착한 data를 메모리로 읽어 온다.
   2. data를 사용할 process에 맞게 적절하게 형태를 바꾼다.
   3. data를 기다리는 process에게 전달하고 process scheduling 요청
   4. process scheduling을 수행

여기서 커널의 동작은 크게 세 부분으로 나눌 수 있으며, 그 처음 부분은 다음과 같다.

   1. device에 도착한 data를 메모리로 읽어 온다.

이 부분은 hardware interrupt를 처리하는 부분으로써 신속하게 device로부터 data를 읽어냄으로써 빠른 시간 내에 device가 외부로부터 다시 data를 받을 수 있게 한다. 일반적으로 이 부분에서는 또 다른 device로부터 오는 hardware interrupt를 허용하지 않음으로써 신속하게 device로부터 data를 읽어낸다. 리눅스 커널에서는 이 부분을 top half라고 하기도 하고 interrupt handler라고도 한다.

다음으로 두 번째 부분은 다음과 같다.

   2. data를 사용할 process에 맞게 적절하게 형태를 바꾼다.
   3. data를 기다리는 process에게 전달하고 process scheduling 요청

이 부분은 기본적으로 hardware interrupt를 허용함으로써 응답성을 좋게 한다. 이 과정은 device에서 읽어온 data를 적당하게 처리해 그 data를 기다리는 process에게 전달하고 필요시 process scheduling을 요청한다. 리눅스 커널에서는 이 부분을 bottom half라고도 하고, deferred work라고도 하고, softirq라고도 한다. 덧붙이자면 3번 동작을 리눅스 커널에서는 wake_up이라고 한다.

마지막으로 세 번째 부분은 다음과 같다.

   4. process scheduling을 수행

이 부분은 두 번째 부분에서 process scheduling을 요청할 경우 수행한다. 리눅스 커널에서는 이 부분을 schedule이라고 한다.

이상에서 hardware interrupt에 의한 커널의 동작은 [그림 2]와 같다.



[그림 2] hardware interrupt에 의한 커널의 일반적인 동작


[그림 1]에서 한 가지 주의할 점은 process Pn의 사용자 영역을 수행하는 중에 들어온 hardware interrupt에 의해 시작한 커널의 동작은 process Pn과 논리적으로 관련이 없다. 따라서 앞에서 설명한 처음 동작과 두 번째 동작을(top half와 bottom half를) 수행하는 중에 현재 process Pn은 논리적으로 멈출 일이 없으며, 따라서 wait queue에 들어갈 일은 없다.

top_half, bottom_half와 system call function간의 통신

마지막으로 한 가지만 더 짚고 넘어가면, [그림 1]에서 process P1의 system call에 의해 시작한 커널과 process Pn의 사용자 영역 수행 중에 발생한 hardware interrupt에 의해 시작한 커널은 각각 논리적으로 독립된 흐름을 가지며 B 부분과 H 부분에서 통신을 한다. 즉, H 부분에서 data를 공급하며, B 부분에서 data를 소비한다. [그림 3]은 [그림 1]의 system call에 의한 커널과 hardware interrupt에 의한 커널간에 data를 주고 받는 상황을 논리적으로 표현한 것이다.



[그림 3] top_half, bottom_half와 system call function간의 통신


system call에 의한 커널의 구체적인 동작 2

[그림 4]를 보면서 다음의 내용을 이해해 보자.
P1, Pn이라 하는 두 process가 있다고 가정하자. process P1는 system call([그림 4]의 A 부분)을 통해 커널에게 process Pn으로부터 data를 받기를 요청할 수 있다. 그러면 커널은 Pn으로부터 P1에게 도착한 data가 있는지 검사한다([그림 4]의 B 부분). P1에게 도착한 data가 없을 경우 커널은 현재 수행중인 process P1을 잠시 멈추기 위해 wait queue에 넣는다([그림 4]의 B 부분). 왜냐하면 process Pn으로부터 data가 도착해야 process P1을 다시 진행할 수 있기 때문이다. 그리고 새로운 process를 선택해([그림 4]의 C 부분) 수행하기 시작한다. 이 동작을 우리는 앞에서 process scheduling이라 했다. 그 이후에 몇 번의 process schduling이 더 있을 수 있다. ([그림 4]에서 process P2에서 process P3로)



[그림 4] system call에 의한 커널의 구체적인 동작


system call에 의한 커널의 구체적인 동작 3

어느 순간 process Pn은 process scheduling에 의해 다시 시작하며([그림 4]의 D 부분) 사용자 영역을 수행하다 system call을 통해([그림 4]의 E 부분) 커널에게 process P1에게 data를 보내기를 요청할 것이다. 그러면 커널은 process Pn으로부터 process P1으로 data를 전달하고([그림 4]의 F 부분), process P1을 wait queue에서 꺼내 ready queue로 넣은 후, ready queue로 새로이 들어간 process P1의 우선순위가 현재 수행 중이던 process Pn의 우선순위보다 클 경우 process scheduling을 요청한 후([그림 4]의 F 부분) process scheduling을 수행한다. process scheduling은 [그림 4]의 G 부분에서 시작해 H 부분에서 끝난다. 즉, process scheduling이 끝나면 process P1이 수행을 다시 시작한다.

system call에 의한 커널의 일반적인 동작
이제 system call에 의해 시작한 커널의 일반적인 동작을 정리해 보자. 먼저 system call은 software interrupt라고도 한다. 주의할 점은 software interrupt는 리눅스 커널내의 bottom half의 또 다른 이름인 softirq와는 관련이 없다.

[그림 1]에서 process P1을 수행하는 중에 들어온 system call에 의해 시작한 커널의 동작은 다음과 같다.

   1. device로부터 data를 읽기를 요청한다.
   2. 현재 수행중인 process를 wait queue에 넣는다
   3. process scheduling을 수행

이 부분은 process의 요청에 의해 커널이 수행하는 영역이며, 상황에 따라 현재 process를 논리적으로 더 이상 진행시킬 수 없는 경우 현재 process를 wait queue에 넣고 process scheduling을 수행할 수 있다. 이 부분은 system call 함수의 일부분이다. 2, 3번 항목은 리눅스 커널의 sleep_on 또는 wait_event와 대응한다.

[그림 4]에서 process P1을 수행하는 중에 들어온 system call에 의해 시작한 커널의 동작은 다음과 같다.

   1. process Pn으로부터 도착한 data가 있는지 검사한다.
   2. 현재 수행중인 process를 wait queue에 넣는다.
   3. process scheduling을 수행

이 부분도 process의 요청에 의해 커널이 수행하는 영역이며, 상황에 따라 현재 process를 논리적으로 더 이상 진행할 수 없는 경우 현재 process를 wait queue에 넣고 process scheduling을 수행한다. 이 부분도 system call 함수의 일부분이다. 여기서도 2, 3번 항목은 리눅스 커널의 sleep_on 또는 wait_event에 대응한다.

[그림 4]에서 process Pn을 수행하는 중에 들어온 system call에 의해 시작한 커널의 동작은 다음과 같다.

   1. process P1에게 data를 전달하고 process scheduling 요청
   2. process scheduling을 수행

여기서는 커널의 동작을 두 부분으로 나눌 수 있으며, 처음 부분은 다음과 같다.

   1. process P1에게 data를 전달하고 process scheduling 요청

이 부분은 process의 요청에 의해 커널이 수행하는 영역이며, system call 함수의 일부분이다. 이 부분은 리눅스 커널의 wake_up에 대응한다.

두 번째 부분은 다음과 같다.

   2. process scheduling을 수행

이 부분은 처음 부분에서 process scheduling을 요청할 경우 수행한다.

이상에서 system call에 의한 커널의 동작은 [그림 5]와 같다.



[그림 5] system call에 의한 커널의 일반적인 동작


[그림 5]에서 process scheduling(1)은, 현재 process P의 요청에 따라 커널이 process P와 관련된 작업을 수행하는 도중에 어떤 조건이 맞지 않아, 예를 들어 필요로 하는 data가 없어서, 더 이상 현재 process P의 작업을 진행할 수 없을 경우, 필요로 하는 조건이 맞을 때까지 현재 process P를 wait queue에 넣어 기다리게 하고 나서 수행하는 process scheduling이며, system call function내에서 수행을 한다. 한 가지 기억해야 할 점은 [그림 1]에서 C와 J 부분이 일반적으로 논리적으로는 연속이지만 시간상으로는 연속이 아니듯이 [그림 5]의 process scheduling(1)도 일반적으로 논리적으로는 연속이지만 시간상으로는 연속이 아니다. 후에 process P가 필요로 하는 조건이 맞으면, process P는 논리적인 흐름이 다른 커널(예를 들어, [그림 1]의 H 부분과 같은)에 의해 ready queue로 옮겨지며, 역시 논리적인 흐름이 다른 커널에서 시작한 process scheduling(예를 들어, [그림 1]의 I 부분과 같은)에 의해 [그림 5]의 process scheduling(1)로 나와 system call function의 나머지 부분을 수행한다. system call function 내에서는 이후에도 필요에 따라 process scheduling이 더 있을 수 있다. 이와는 달리 process scheduling(2)는 커널이 process P의 요청에 의해 system call function을 수행하는 도중에 wait queue에서 기다리던 임의의 process를 ready queue로 넣고, 그 ready queue에 넣은 process의 우선 순위가 현재 process P의 우선 순위보다 클 경우(예를 들어 [그림 4]의 F 부분과 같은)에 수행하는 process scheduling이다. 이 경우 현재 process P는 ready queue에 그대로 남아 있다.

system call function과 system call function간의 통신

마지막으로 한 가지만 더 짚고 넘어가면, [그림 4]에서 process P1의 system call에 의해 시작한 커널과 process Pn의 system call에 의해 시작한 커널은 각각 논리적으로 독립된 흐름을 가지며 B 부분과 F 부분에서 통신을 한다. 즉, F 부분에서 data를 공급하며, B 부분에서 data를 소비한다. [그림 6]은 [그림 4]의 system call에 의한 커널간에 data를 주고 받는 상황을 논리적으로 표현한 것이다.



[그림 6] system call function과 system call function간의 통신


process scheduling의 시작과 끝

우리는 [그림 2]와 [그림 5]에서 커널의 일반적인 동작과 process scheduling이 언제 수행되는지 보았다. 아래 [그림 7]에서 process scheduling이 시작되는 부분과 끝나는 부분이 어떻게 연결될 수 있는지 자세히 살펴 보자.
어떤 process의 a 부분에서 시작한 process scheduling은 임의의 다른 process의 b, d, f부분에서 끝날 수 있다. 또 어떤 process의 c 부분에서 시작한 process scheduling도 임의의 다른 process의 b, d, f 부분에서 끝날 수 있다. 마지막으로 어떤 process의 e 부분에서 시작한 process scheduling 역시 임의의 다른 process의 b, d, f 부분에서 끝날 수 있다.



[그림 7] process scheduling의 시작과 끝


지금까지 우리는 커널이 수행해야 할 일반적인 동작이 무엇인지 살펴 보았다. 즉, system call을 통해 시작한 커널의 동작, hardware interrupt에 의해 시작한 커널의 동작을 보았다. 이 과정에서 process와 device 사이에서 커널이 수행해야 할 역할이란 것이 우리가 모르는 그 어떤 것이 아니란 점도 느꼈을 것이다. 의외로 커널의 역할이 지극히 당연한 것들이라고 느꼈을 수도 있다. 또 hardware interrupt에 의해 시작한 커널과 system call에 의해 시작한 커널간의 통신, system call에 의해 시작한 커널과system call에 의해 시작한 커널간의 통신을 보았다. 이 과정에서 논리적으로 서로 독립적인 커널의 동작간에 통신이 어떻게 이루어지는지 구체적으로 알았을 것이다.
리눅스 커널의 이해(5): 디바이스에 쓰기 동작에 대한 구체적인 작성 예  
등록: 한빛미디어(주) (2005-06-22 15:57:51)

저자: 서민우
출처: Embedded World

[ 관련 기사 ]
리눅스 커널의 이해(1) : 커널의 일반적인 역할과 동작
리눅스 커널의 이해(2): 리눅스 커널의 동작
리눅스 커널의 이해(3): 리눅스 디바이스 작성시 동기화 문제
리눅스 커널의 이해(4): Uni-Processor & Multi-Processor 환경에서의 동기화 문제

이 번 기사에서는 [디바이스에 쓰기 동작]에 대한 구체적인 작성 예를 살펴보고, 동기화 문제에 대한 처리를 적절히 해 주지 않을 경우 어떤 문제가 발생하는지 보기로 하자. 또한 지난 기사에서 살펴 보았던 동기화 문제에 대한 해결책을 이용하여 발생하는 문제점을 해결해 보기로 하자.

다음은 [디바이스에 쓰기 동작]을 중심으로 작성한 리눅스 디바이스 드라이버의 한 예다. 여기서는 독자가 모듈 형태의 리눅스 디바이스 드라이버를 작성할 줄 알고, 동적으로 리눅스 커널에 모듈을 삽입할 줄 안다고 가정한다.


# vi devwrite.c
#include
#include

#include
#include
#include

ssize_t dev_write(struct file * filp, const char * buffer,
size_t length, loff_t * offset);

struct file_operations dev_fops = {
write:   dev_write,
};

static int major = 0;
int init_module()
{
        printk("Loading devwrite module\n");
        major = register_chrdev(0, "devwrite", &dev_fops);
        if(major < 0) return major;
        return 0;
}

void cleanup_module()
{
        unregister_chrdev(major, "devwrite");
        printk("Unloading devwrite module\n");
}

#define SLOT_NUM    8

char dev_buffer;
int dev_key = 1;

char data_slot[SLOT_NUM];
int full_slot_num = 0;
int empty_slot_num = SLOT_NUM;
int full_slot_pos = 0;
int empty_slot_pos = 0;

void dev_working();

ssize_t dev_write(struct file * filp, const char * buffer,
size_t length, loff_t * offset)
{
        char user_buffer;

        if(length != 1) return -1;

        copy_from_user(&user_buffer, buffer, 1);

        if(dev_key == 0) {                                                   // ①
                if(empty_slot_num <= 0) return -1;                    // ④ start
                empty_slot_num --;

                data_slot[empty_slot_pos] = user_buffer;
                empty_slot_pos ++;
                if(empty_slot_pos == SLOT_NUM)
empty_slot_pos = 0;

                full_slot_num ++;                                             // ④ end
                return 1;

        }
        dev_key = 0;                                                           // ②
        dev_buffer = user_buffer;                                          // ③

        dev_working();                                                       // ⑤

        return 1;
}

static struct timer_list dev_interrupt;
void dev_interrupt_handler(unsigned long dataptr);

void dev_working()
{
        init_timer(&dev_interrupt);                                        // ⑨

        dev_interrupt.function = dev_interrupt_handler;            // ⑦
        dev_interrupt.data = (unsigned long)NULL;
        dev_interrupt.expires = jiffies + 1;                              // ⑥

        add_timer(&dev_interrupt);                                       // ⑧
}

void dev_interrupt_handler(unsigned long dataptr)
{
        printk("%c\n", dev_buffer);
       
        if(full_slot_num <= 0) {dev_key = 1; return;}                // ⑩
        full_slot_num --;                                                      // ⑪ start  

        dev_buffer = data_slot[full_slot_pos];
        full_slot_pos ++;
        if(full_slot_pos == SLOT_NUM) full_slot_pos = 0;

        empty_slot_num ++;                                                  // ⑪ end

        dev_working();

        return;
}


그러면 동기화 문제와 관련한 부분을 중심으로 소스를 살펴 보자.

dev_write 함수는 write 시스템 콜 함수에 의해 시스템 콜 루틴 내부에서 수행된다. dev_write 함수에서 ①, ②, ③ 부분은 논리적으로 다음과 같다.


디바이스를 사용하고 있지 않으면
    디바이스를 사용한다고 표시하고
    데이터를 디바이스 버퍼에 쓰고 나간다


③ 부분에서 dev_buffer 변수는 가상 디바이스의 버퍼를 나타낸다. 그리고 ①과 ② 부분에서 사용한 dev_key 변수는 가상 디바이스의 버퍼를 하나 이상의 프로세스가 동시에 접근하지 못하게 하는 역할을 한다.

우리는 전월 호에서 이와 같은 루틴에서 발생하는 동기화 문제를 다음과 같이 처리할 수 있음을 보았다.


cli
디바이스를 사용하고 있지 않으면
    디바이스를 사용한다고 표시하고
    데이터를 디바이스 버퍼에 쓰고 나간다
    sti


리 눅스 커널에는 cli와 sti에 해당하는 local_irq_save와 local_irq_restore라는 매크로가 있다. 이 두 매크로를 이용하여 dev_write 함수의 ①, ②, ③ 부분에서 발생할 수 있는 동기화 문제를 다음과 같이 처리할 수 있다.


unsigned long flags;
local_irq_save(flags);
if(dev_key == 0) {

}
dev_key = 0;
dev_buffer = user_buffer;
local_irq_restore(flags);


여 기서 local_irq_save(flags) 매크로는 CPU 내에 있는 flag 레지스터를 flags 지역 변수에 저장한 다음에 인터럽트를 끄는 역할을 한다. local_irq_restore(flags) 매크로는 flags 지역 변수의 값을 CPU 내에 있는 flag 레지스터로 복구함으로써 인터럽트를 켜는 역할을 한다.

또 dev_write 함수에서 ①과 ④ 부분은 논리적으로 다음과 같다.


디바이스를 사용하고 있으면
    데이터를 데이터 큐에 넣고 나간다


④ 부분에서 data_slot 배열 변수는 원형 데이터 큐를 나타낸다. empty_slot_pos 변수는 데이터를 채워 넣어야 할 큐의 위치를 나타낸다. empty_slot_num 변수는 큐의 비어 있는 데이터 공간의 개수를 나타낸다. 그래서 큐에 데이터를 채워 넣기 전에 empty_slot_num 변수의 값을 하나 감소시킨다. full_slot_num 변수는 큐에 채워진 데이터 공간의 개수를 나타낸다. 그래서 큐에 데이터를 채워 넣은 후에 full_slot_num 변수의 값을 하나 증가시킨다.

우리는 전월 호에서 이와 같은 루틴에서 발생하는 동기화 문제를 다음과 같이 처리할 수 있음을 보았다.


cli
디바이스를 사용하고 있으면
    데이터를 데이터 큐에 넣고 나간다
    sti


따라서 dev_write 함수의 ①과 ④ 부분에서 발생할 수 있는 동기화 문제를 다음과 같이 처리할 수 있다.


unsigned long flags;
local_irq_save(flags);
if(dev_key == 0) {
           if(empty_slot_num <= 0) {
                      local_irq_restore(flags);
                      return -1;
           }
           empty_slot_num --;

           data_slot[empty_slot_pos] = user_buffer;
           empty_slot_pos ++;
           if(empty_slot_pos == SLOT_NUM)
                       empty_slot_pos = 0;

           full_slot_num ++;
           local_irq_restore(flags);
           return 1;
}


⑤ 부분은 ③ 부분에서 디바이스 버퍼에 데이터를 쓰고 나면, 디바이스가 동작하기 시작함을 논리적으로 나타낸다.




[그림 1] 디바이스에 쓰기 예


앞 에서 우리는 ③ 부분에서 가상 디바이스의 버퍼를 사용한다고 했다. 따라서 이 디바이스에 의한 hardware interrupt는 발생할 수 없다. 그래서 여기서는 주기적으로 발생하는 timer interrupt를 가상 디바이스에서 발생하는 hardware interrupt라고 가정한다. 그럴 경우 timer interrupt는 [그림 1]과 같이 발생할 수 있으며, 이 그림은 전월호의 [그림 2]와 논리적으로 크게 다르지 않음을 볼 수 있다.

[그림 1]에서 ⓐ 부분은 dev_working 함수의 ⑥ 부분을 나타낸다. 여기서는 dev_interrupt 구조체 변수의 멤버 변수인 expires 변수 값을 커널 변수인 jiffies 변수 값에 1을 더해서 설정한다. jiffies 변수는 커널 변수로 주기적으로 발생하는 timer interrupt를 처리하는 루틴의 top_half 부분에서 그 값을 하나씩 증가시킨다. 리눅스 커널 버전 2.6에서는 초당 1000 번 timer interrupt가 발생하도록 설정되어 있다.

[그림 1]의 ⓑ 부분에서는 jiffies 변수 값을 증가시키고 있다. jiffies 변수 값을 증가시키는 함수는 do_timer 함수이며, timer interrupt handler 내에서 이 함수를 호출한다. do_timer 함수는 리눅스 커널 소스의 linux/kernel/timer.c 파일에서 찾을 수 있다.

[그림 1]의 ⓒ 부분에서는 dev_interrupt 구조체 변수의 expires 변수 값과 현재의 jiffies 변수 값을 비교하여 작거나 같으면 dev_interrupt 구조체 변수의 function 함수 포인터 변수가 가리키는 함수를 수행한다. 이 부분은 timer interrupt를 처리하는 루틴의 bottom_half 부분이며 timer_bh 함수 내에서 run_timer_list 함수를 호출하여 수행한다. timer_bh 함수는 리눅스 커널 소스의 linux/kernel/timer.c 파일에서 찾을 수 있다. [그림 1]의 ⓒ 부분에서는 dev_working 함수의 ⑦ 부분에 의해 실제로는 dev_interrupt_handler 함수가 수행된다.

dev_interrupt_handler 함수를 살펴보기 전에 timer_bh 함수 내의 run_timer_list 함수의 역할을 좀 더 보기로 하자. run_timer_list 함수는 timer_list 구조체 변수로 이루어진 linked list에서 timer_list 구조체 변수를 소비하는 역할을 한다. 구체적으로 timer_list 구조체 변수의 expires 변수 값이 현재 jiffies 변수 값보다 작거나 같을 경우 해당하는 timer_list 구조체 변수를 linked list에서 떼내어, timer_list 구조체 변수의 function 포인터 변수가 가리키는 함수를 수행한다. dev_working 함수의 ⑧ 부분에서 사용한 add_timer 함수는 커널 함수이며 run_timer_list 함수가 소비하는 linked list에 timer_list 구조체 변수를 하나 더해 주는 생산자 역할을 한다. dev_working 함수의 ⑨ 부분에서 사용한 init_timer 함수는 timer_list 구조체 변수를 초기화해주는 커널 함수이다.

그러면 dev_interrupt_handler 함수를 보기로 하자. dev_interrupt_handler 함수는 가상 디바이스의 top_half 루틴과 bottom_half 루틴을 나타낸다. dev_interrupt_handler 함수에서 ⑩ 부분은 논리적으로 다음과 같다.


데이터 큐가 비어 있으면
    디바이스를 다 사용했다고 표시하고 나간다


또 dev_interrupt_handler 함수에서 ⑩과 ⑪ 부분은 논리적으로 다음과 같다.


데이터 큐가 비어 있지 않으면
    데이터를 하나 꺼내서
    디바이스 버퍼에 쓰고 나간다


⑪ 부분에서 full_slot_pos 변수는 데이터를 비울 큐의 위치를 나타낸다.

이상에서 dev_write 함수에서 동기화 문제가 발생할 수 있으며 다음과 같이 해결할 수 있다.


ssize_t dev_write(struct file * filp, const char * buffer,
                       size_t length, loff_t * offset)
{
            char user_buffer;
            unsigned long flags;

            if(length != 1) return -1;

            copy_from_user(&user_buffer, buffer, 1);

            local_irq_save(flags);
            if(dev_key == 0) {
                       if(empty_slot_num <= 0) {
                                   local_irq_restore(flags);
                                   return -1;
                       }
                       empty_slot_num --;

                       data_slot[empty_slot_pos] = user_buffer;
                       empty_slot_pos ++;
                       if(empty_slot_pos == SLOT_NUM)
                                    empty_slot_pos = 0;

                       full_slot_num ++;
                       local_irq_restore(flags);
                       return 1;
            }
            dev_key = 0;
            dev_buffer = user_buffer;
            local_irq_restore(flags);
       
            dev_working();

            return 1;
}


완 성된 소스를 다음과 같이 컴파일한 후 insmod 명령어를 이용하여 커널에 devwrite.o 모듈을 끼워 넣는다. 컴파일하는 부분에서 –D__KERNEL__ 옵션은 #define __KERNEL__ 이라는 매크로 문장을 컴파일하고자 하는 파일의 맨 위쪽에 써 넣는 효과와 같으며, __KERNEL__ 매크로는 컴파일하는 소스가 커널의 일부가 될 수 있다는 의미를 가진다. MODULE 매크로는 컴파일하는 소스를 커널에 모듈형태로 동적으로 끼워 넣거나 빼 낼 수 있다는 의미이다. -I/usr/src/linux-2.4/include 옵션은 파일 내에서 참조하는 헤더파일을 찾을 디렉토리를 나타낸다. 일반적으로 PC 상에서 리눅스 커널 소스를 설치할 경우 /usr/src 디렉토리 아래 linux 내지는 linux-2.4 와 같은 디렉토리 아래 놓인다. 모듈 프로그램은 커널의 일부가 되어 동작하며 따라서 그 모듈이 동작할 커널을 컴파일하는 과정에서 참조했던 헤더파일을 참조해야 한다.


# gcc devwrite.c -c -D__KERNEL__ -DMODULE -I/usr/src/linux-2.4/include
# lsmod
# insmod devwrite.o -f
# lsmod
Module                         Size  Used by    Tainted: PF
devwrite                        3581   0  (unused)


/proc/devices 파일은 커널내의 디바이스 드라이버에 대한 정보를 동적으로 나타낸다. 이 파일을 들여다보면 방금 끼워 넣은 디바이스 드라이버의 주 번호가 253임을 알 수 있다. 주 번호는 바뀔 수도 있으니 주의하기 바란다.


# cat /proc/devices
Character devices:
              ...
253 devwrite
              ...
Block devices:


우리가 작성한 디바이스 드라이버를 접근하기 위해 문자 디바이스 파일을 다음과 같이 만든다.


# mknod /dev/devwrite c 253 0
# ls -l /dev/devwrite
crw-r--r--    1 root     root     253,   0  7월 12 00:03 /dev/devwrite


그리고 우리가 작성한 디바이스 드라이버를 사용할 응용 프로그램을 다음과 같이 작성한다.


# vi devwrite-app.c
#include
#include
#include

int main()
{
        int fd;

        fd = open("/dev/devwrite", O_RDWR);

        write(fd, "A", 1);

        close(fd);
}


그리고 다음과 같이 응용 프로그램을 컴파일한 후 응용 프로그램을 수행해 본다. 화면에는 아무 내용도 뜨지 않는다.


# gcc devwrite-app.c -o devwrite-app
# ./devwrite-app


이 젠 모듈을 커널에서 빼낸후 /var/log/messages 파일의 맨 뒷부분을 읽어 본다. 각각 insmod 명령어를 수행하는 과정에서 커널내에서 수행한 init_module 함수, 좀 전에 수행한 응용 프로그램을 수행하는 과정에서 커널내에서 수행한 dev_interrupt_handler 함수, rmmod 명령어를 수행하는 과정에서 커널내에서 수행한 cleanup_module 함수에서 찍은 메시지를 볼 수 있다.


# rmmod devwrite
# tail /var/log/messages
...
Jul 12 00:16:44 localhost kernel: Loading devwrite module
Jul 12 00:17:00 localhost kernel: A
Jul 12 00:17:13 localhost kernel: Unloading devwrite module


그 러면 위와 같이 동기화 문제를 처리 하지 않을 경우 어떤 문제가 발생할 수 있는지 예를 하나 보기로 하자. 다음 예는 전월호의 [그림 5]와 [그림 6]의 경우에서 보았던 루틴간 경쟁 상태를 발생시킨다. 먼저 dev_write 함수와 dev_interrupt_handler 함수를 각각 다음과 같이 고친다.


ssize_t dev_write(struct file * filp, const char * buffer,
                       size_t length, loff_t * offset)
{
             char user_buffer;
             int i;

             if(length != 1) return -1;

             copy_from_user(&user_buffer, buffer, 1);

             for(i=0;i<600;i++) {                                             // ⑫-⑴
                        user_buffer = (char)(i%10)+48;

                        if(dev_key == 0) {
                                   printk("<--1\n");                         // ⑬-⑴
                                   if(empty_slot_num <= 0) {
                                               printk("slot is full\n");
                                               while(1) if(empty_slot_num > 0) break;         // ⑫-⑵
                                   }
                                   empty_slot_num --;

                                   data_slot[empty_slot_pos] = user_buffer;
                                   empty_slot_pos ++;
                                   if(empty_slot_pos == SLOT_NUM)
                                               empty_slot_pos = 0;

                                   {
                                               int j;
                                               for(j=0;j<0x1000000;j++);
                                    }
                                    printk("<--2\n");                       // ⑬-⑵
                                    full_slot_num ++;

                                    continue;                                  // ⑫-⑶
                        }
                        dev_key = 0;
                        dev_buffer = user_buffer;

                        dev_working();
                }

                return 1;
}

void dev_interrupt_handler(unsigned long dataptr)
{
                printk("%c\n", dev_buffer);

                if(full_slot_num <= 0) {
                         printk("no data\n");
                         dev_key = 1;
                         return;
                }
                full_slot_num --;

                dev_buffer = data_slot[full_slot_pos];
                full_slot_pos ++;
                if(full_slot_pos == SLOT_NUM) full_slot_pos = 0;

                empty_slot_num ++;

                dev_working();

                return;
}


dev_write 함수의 ⑫-⑴, ⑫-⑵, ⑫-⑶ 부분은 동기화 문제가 발생할 수 있는 영역을 반복적으로 수행함으로써 dev_interrupt_handler 함수와 충돌이 날 가능성을 높이는 역할을 한다. dev_write 함수의 ⑬-⑴, ⑬-⑵ 사이에 dev_interrupt_handler 함수가 끼어 들 경우 문제가 발생한다. ⑭ 부분은 ⑬-⑴, ⑬-⑵ 사이에 dev_interrupt_handler 함수가 끼어 들 가능성을 높이기 위해 끼워 넣었다. 다음과 같이 테스트해 본다.


# gcc devwrite.c -c -D__KERNEL__ -DMODULE -I/usr/src/linux-2.4/include
# insmod devwrite.o -f
# ./devwrite-app
# tail /var/log/messages -n 600
...
Jul 12 06:19:46 localhost kernel: <--1                  ⒜
Jul 12 06:19:46 localhost kernel: 6
Jul 12 06:19:46 localhost kernel: no data              ⒞
Jul 12 06:19:46 localhost kernel: <--2                  ⒝
Jul 12 06:19:46 localhost kernel: <--1
Jul 12 06:19:46 localhost kernel: <--2
Jul 12 06:19:46 localhost kernel: <--1
Jul 12 06:19:46 localhost kernel: 8                       ⒟
Jul 12 06:19:46 localhost kernel: <--2
Jul 12 06:19:46 localhost kernel: <--1
Jul 12 06:19:46 localhost kernel: 7                       ⒠
Jul 12 06:19:46 localhost kernel: <--2
...
# rmmod devwrite


테스트를 수행한 결과 ⒜와 ⒝ 사이에 ⒞가 끼어 듦으로써 동기화의 문제가 발생하였다. 그 결과 ⒟와 ⒠에서 8과 7의 데이터 역전 현상이 발생하였으며, 또한 7 데이터에 starvation이 발생하였음을 알 수 있다.

그럼 여기서 발생한 동기화 문제를 해결해 보자. 먼저 dev_write 함수를 다음과 같이 고친다.


ssize_t dev_write(struct file * filp, const char * buffer,
                size_t length, loff_t * offset)
{
        char user_buffer;
        int i;
        unsigned long flags;

        if(length != 1) return -1;

        copy_from_user(&user_buffer, buffer, 1);

        for(i=0;i<600;i++) {
                user_buffer = (char)(i%10)+48;

                local_irq_save(flags);
                if(dev_key == 0) {
                        printk("<--1\n");
                        if(empty_slot_num <= 0) {
                                local_irq_restore(flags);
                                printk("slot is full\n");
                                while(1) if(empty_slot_num > 0) break;
                        }
                        empty_slot_num --;

                        data_slot[empty_slot_pos] = user_buffer;
                        empty_slot_pos ++;
                        if(empty_slot_pos == SLOT_NUM)
                                empty_slot_pos = 0;

                        {
                                int j;
                                for(j=0;j<0x1000000;j++);
                        }
                        printk("<--2\n");
                        full_slot_num ++;
                        local_irq_restore(flags);

                        continue;
                }
                dev_key = 0;
                dev_buffer = user_buffer;
                local_irq_restore(flags);

                dev_working();
        }

        return 1;
}


다음과 같이 테스트를 수행하다.


# gcc devwrite.c -c -D__KERNEL__ -DMODULE -I/usr/src/linux-2.4/include
# insmod devwrite.o -f
# ./devwrite-app
# tail /var/log/messages -n 600
...
Nov 19 18:41:07 localhost kernel: 0
Nov 19 18:41:07 localhost kernel: <--1
Nov 19 18:41:07 localhost kernel: <--2
Nov 19 18:41:07 localhost kernel: 1
Nov 19 18:41:07 localhost kernel: <--1
Nov 19 18:41:07 localhost kernel: <--2
Nov 19 18:41:07 localhost kernel: 2
Nov 19 18:41:07 localhost kernel: <--1
Nov 19 18:41:07 localhost kernel: <--2
Nov 19 18:41:07 localhost kernel: 3
Nov 19 18:41:07 localhost kernel: <--1
Nov 19 18:41:07 localhost kernel: <--2
Nov 19 18:41:07 localhost kernel: 4
Nov 19 18:41:07 localhost kernel: <--1
Nov 19 18:41:07 localhost kernel: <--2
Nov 19 18:41:07 localhost kernel: 5
Nov 19 18:41:07 localhost kernel: <--1
Nov 19 18:41:07 localhost kernel: <--2
Nov 19 18:41:07 localhost kernel: 6
Nov 19 18:41:07 localhost kernel: <--1
Nov 19 18:41:07 localhost kernel: <--2
Nov 19 18:41:07 localhost kernel: 7
Nov 19 18:41:07 localhost kernel: <--1
Nov 19 18:41:07 localhost kernel: <--2
Nov 19 18:41:07 localhost kernel: 8
Nov 19 18:41:07 localhost kernel: <--1
Nov 19 18:41:07 localhost kernel: <--2
Nov 19 18:41:07 localhost kernel: 9
...


데이터의 역전 현상이나 starvation 없이 순서대로 data가 가상 디바이스에 전달되는걸 볼 수 있다.

이 상에서 <디바이스에 쓰기 동작>에 대한 구체적인 작성 예를 보았다. 참고로 모듈 프로그래밍은 일반적으로 루트 사용자의 권한으로 해야 한다. 본 기사에서는 리눅스 커널 2.6 버전의 내용을 위주로 동기화의 문제를 다루고 있지만, 실제 동기화에 대한 테스트는 리눅스 커널 2.4 버전의 파란 리눅스 7.3에서 하였으니 이 점 주의 하기 바란다.


rpm으로 풀고자 하는 파일들을 gzip으로 압축을 한다.
ex) mkdir medic-1.0.0
      cp medic_test/medic medic_test/libdfb_grp.so.0.0.0 medic-1.0.0
      cd medic-1.0.0
      ln -s libdfb_grp.so.0.0.0 libdfb_grp.so
      ln -s libdfb_grp.so.0.0.0 libdfb_grp.so.0
      cd ..
      tar cvfz medic-1.0.0.tar.gz medic-1.0.0
그리고 압축된 파일을 rpm만들고자 하는 딜렉토리로 옮긴다.
      cp medic-1.0.0.tar.gz /usr/src/booyo/SOURCES
이제 Spec파일을 만들차례다.
      cd /usr/src/booyo/SPEC
      vi medic-1.0.0.spec


%define version 0.0.1
%define name medic

Summary: Medical equipment
Name:  %{name}
Version: %{version}
Release: 1
Prefix:  /root
Group:  /root
Source:  %{name}-%{version}.tar.gz
BuildRoot: /var/tmp/rpm/%{name}-%{version}
License: GPL
URL:  http://www.etri.re.kr

%description

%prep
%setup
mkdir -p $RPM_BUILD_ROOT/root/medic
cp -af /usr/src/booyo/BUILD/%{name}-%{version}/%{name} $RPM_BUILD_ROOT/root/medic/%{name}
mkdir -p $RPM_BUILD_ROOT/lib
cp -af /usr/src/booyo/BUILD/%{name}-%{version}/libdfb_grp.so.0.0.0 $RPM_BUILD_ROOT/lib/libdfb_grp.so.0.0.0
mv /usr/src/booyo/BUILD/%{name}-%{version}/libdfb_grp.so $RPM_BUILD_ROOT/lib/libdfb_grp.so
mv /usr/src/booyo/BUILD/%{name}-%{version}/libdfb_grp.so.0 $RPM_BUILD_ROOT/lib/libdfb_grp.so.0
#ln -s $RPM_BUILD_ROOT/lib/libdfb_grp.so.0.0.0 $RPM_BUILD_ROOT/lib/libdfb_grp.so
#ln -s $RPM_BUILD_ROOT/lib/libdfb_grp.so.0.0.0 $RPM_BUILD_ROOT/lib/libdfb_grp.so.0

%files
%{prefix}/medic/%{name}
/lib/libdfb_grp.so.0.0.0
/lib/libdfb_grp.so.0
/lib/libdfb_grp.so

%clean
rm -rf $RPM_BUILD_ROOT

이제 모든 준비는 끝났다.
바이너리 RPM을 생성해 보자.
     cd /usr/src/booyo/SPEC
     rpmbuild -ba --target=armv4l medic-1.0.0.spec

이렇게 하면 /usr/src/booyo/RPMS/armv4l/medic-1.0.0.armv4l.rpm 이 생성됨을 알수 있다.
우리는 이제 이것을 배포하기만 하면 된다. (근데 아직 테스트를 해보지 않아서 잘될지 알수가 없구나~~)

참조 : http://www.coffeenix.net/doc/HOWTOs/html/RPM-HOWTO/RPM-HOWTO-7.html
         http://blog.naver.com/yong0872?Redirect=Log&logNo=70001568086

# rpm -Uvh gtk+-2.0.0-1.armv4l.rpm
이건 현재 host에 설치하라는 소리인데~~~~
rpm2cpio xxx.rpm | cpio -i --make-directories
or
rpm2cpio xxx.rpm | cpio -id

이런식으로 하면 현재 디렉토리에gtk+-2.0.0-1.armv4l.rpm 이 풀린다...
풀린 rpm을 이제 타겟보드에 포팅하면 된다. ^^

#include <stdio.h>

int main()
{
        FILE * tp;
        char lines[1024];
        int j=0;

        tp = popen("top -b -n1 | sed -n 3p | awk '{print $2}'", "r");

        while(fgets(lines, 1024, tp))
        {
                if (lines[j] != ' ') j++;
        }
        printf("%s\r\n", lines);
        pclose(tp);
        return 0;
}

참조 : directfb예제중 df_cpuload.c 를 이용.
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <gtk/gtk.h>

#define SET_IF_DESIRED(x,y) do{  if(x) *(x) = (y); }while(0)
#define JT unsigned long

static void four_cpu_numbers(int *uret, int *nret, int *sret, int *iret);
static int get_load();
void* cpu_load(int t);


GThread *adc_insert_thrs;
GError *adc_error;


static void four_cpu_numbers(int *uret, int *nret, int *sret, int *iret)
{
     int       tmp_u, tmp_n, tmp_s, tmp_i;
     static JT old_u, old_n, old_s, old_i, old_wa, old_hi, old_si;
     JT        new_u, new_n, new_s, new_i, new_wa = 0, new_hi = 0, new_si = 0;
     JT        ticks_past; /* avoid div-by-0 by not calling too often :-( */
     char      dummy[16];
     FILE     *stat;

     stat = fopen ("/proc/stat", "r");
     if (!stat)
          return;

     if (fscanf (stat, "%s %lu %lu %lu %lu %lu %lu %lu", dummy,
                 &new_u, &new_n, &new_s, &new_i, &new_wa, &new_hi, &new_si) < 5)
     {
          fclose (stat);
          return;
     }

     fclose (stat);

     ticks_past = ((new_u + new_n + new_s + new_i + new_wa + new_hi + new_si) -
                   (old_u + old_n + old_s + old_i + old_wa + old_hi + old_si));
     if (ticks_past) {
          tmp_u = ((new_u - old_u) << 16) / ticks_past;
          tmp_n = ((new_n - old_n) << 16) / ticks_past;
          tmp_s = ((new_s - old_s) << 16) / ticks_past;
          tmp_i = ((new_i - old_i) << 16) / ticks_past;
     }
     else {
          tmp_u = 0;
          tmp_n = 0;
          tmp_s = 0;
          tmp_i = 0;
     }

     SET_IF_DESIRED(uret, tmp_u);
     SET_IF_DESIRED(nret, tmp_n);
     SET_IF_DESIRED(sret, tmp_s);
     SET_IF_DESIRED(iret, tmp_i);

     old_u  = new_u;
     old_n  = new_n;
     old_s  = new_s;
     old_i  = new_i;
     old_wa = new_wa;
     old_hi = new_hi;
     old_si = new_si;
}
#undef JT

static int
get_load()
{
     static int old_load = 0;

     int u = 0, n = 0, s = 0, i, load;

     four_cpu_numbers( &u, &n, &s, &i );

     //load = u + n + s;
     load = u + n + s + i;

     //old_load = (load + load + load + old_load) >> 2;
     old_load = ((u+n+s)*100) / (load);

     //return old_load >> 10;
     return old_load;
}

void* cpu_load(int t)
{
 int load;
 while(1){
  usleep(100000);
  load = get_load();
  printf("cpu load = %d\n",load);
 }
 
 return (void*)0;
}

int main(int argc, char** argv)
{
 int load;
 g_thread_init(NULL);
 adc_insert_thrs = g_thread_create((GThreadFunc)cpu_load,0,TRUE,&adc_error);
 
 g_thread_join(adc_insert_thrs );
 /*
 while(1){
  usleep(100000);
  load = get_load();
  printf("cpu load = %d\n",load);
 }
 //*/
 
  return 0;
}

1 CPU load
2 --------
3
4 Linux exports various bits of information via `/proc/stat' and
5 `/proc/uptime' that userland tools, such as top(1), use to calculate
6 the average time system spent in a particular state, for example:
7
8     $ iostat
9     Linux 2.6.18.3-exp (linmac)     02/20/2007
10
11     avg-cpu:  %user   %nice %system %iowait  %steal   %idle
12               10.01    0.00    2.92    5.44    0.00   81.63
13
14     ...
15
16 Here the system thinks that over the default sampling period the
17 system spent 10.01% of the time doing work in user space, 2.92% in the
18 kernel, and was overall 81.63% of the time idle.
19
20 In most cases the `/proc/stat' information reflects the reality quite
21 closely, however due to the nature of how/when the kernel collects
22 this data sometimes it can not be trusted at all.
23
24 So how is this information collected?  Whenever timer interrupt is
25 signalled the kernel looks what kind of task was running at this
26 moment and increments the counter that corresponds to this tasks
27 kind/state.  The problem with this is that the system could have
28 switched between various states multiple times between two timer
29 interrupts yet the counter is incremented only for the last state.
30
31
32 Example
33 -------
34
35 If we imagine the system with one task that periodically burns cycles
36 in the following manner:
37
38 time line between two timer interrupts
39 |--------------------------------------|
40 ^                                    ^
41 |_ something begins working          |
42                                       |_ something goes to sleep
43                                      (only to be awaken quite soon)
44
45 In the above situation the system will be 0% loaded according to the
46 `/proc/stat' (since the timer interrupt will always happen when the
47 system is executing the idle handler), but in reality the load is
48 closer to 99%.
49
50 One can imagine many more situations where this behavior of the kernel
51 will lead to quite erratic information inside `/proc/stat'.
52
53
54 /* gcc -o hog smallhog.c */
55 #include <time.h>
56 #include <limits.h>
57 #include <signal.h>
58 #include <sys/time.h>
59 #define HIST 10
60
61 static volatile sig_atomic_t stop;
62
63 static void sighandler (int signr)
64 {
65      (void) signr;
66      stop = 1;
67 }
68 static unsigned long hog (unsigned long niters)
69 {
70      stop = 0;
71      while (!stop && --niters);
72      return niters;
73 }
74 int main (void)
75 {
76      int i;
77      struct itimerval it = { .it_interval = { .tv_sec = 0, .tv_usec = 1 },
78                              .it_value = { .tv_sec = 0, .tv_usec = 1 } };
79      sigset_t set;
80      unsigned long v[HIST];
81      double tmp = 0.0;
82      unsigned long n;
83      signal (SIGALRM, &sighandler);
84      setitimer (ITIMER_REAL, &it, NULL);
85
86      hog (ULONG_MAX);
87      for (i = 0; i < HIST; ++i) v[i] = ULONG_MAX - hog (ULONG_MAX);
88      for (i = 0; i < HIST; ++i) tmp += v[i];
89      tmp /= HIST;
90      n = tmp - (tmp / 3.0);
91
92      sigemptyset (&set);
93      sigaddset (&set, SIGALRM);
94
95      for (;;) {
96          hog (n);
97          sigwait (&set, &i);
98      }
99      return 0;
100 }
101
102
103 References
104 ----------
105
106 http://lkml.org/lkml/2007/2/12/6
107 Documentation/filesystems/proc.txt (1.8)
108
109
110 Thanks
111 ------
112
113 Con Kolivas, Pavel Machek
참조 : http://www.joinc.co.kr/modules/moniwiki/wiki.php/Site/QOS/SMS/Cpu_Usage
// Standard C++ Library
#include <iostream>

// Common Library
#include <cinterface.h>

// Standard C Library
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <ctype.h>

#define K4 0
#define K6 1

using namespace std;

int isTotal=0;
int findCpu=0;

int GetCpuNum();
int Type = 0;

struct _CpuStat
{
    unsigned int User;
    unsigned int System;
    unsigned int Idle;
};

struct _CpuStat CpuStat[2];

char *rtvstr;

int Init()
{
    GetCpuNum();
    rtvstr = (char *)malloc(80);
    return 1;
}

int cidx = 0;

// CPU 갯수를 얻어온다.
int GetCpuNum()
{
    FILE *fp;
    fp = fopen("/proc/stat", "r");
    char buf[80];
    isTotal=0;
    findCpu=0;
    if (fp == NULL)
    {
        return 0;
    }
    while(fgets(buf, 80, fp))
    {
        if (strncmp(buf, "cpu", 3) != 0)
        {
            continue;
        }
        if (isdigit(buf[3]))
        {
            findCpu++;
        }
        else
        {
            isTotal = 1;
        }
    }
    fclose(fp);
    return 1;
}

// Cpu 갯수를 리턴한다.
int NumRows()
{
    return findCpu;
}

// proc 파일시스템을 분석해서 Cpu 사용율 정보를 얻어온다.
// /proc/stat 의 사용율 총합라인만 읽어올 것이다.
char *Read()
{
    FILE *fp;
    char buf[80];
    fp = fopen("/proc/stat", "r");
    char cpuid[8];
    int nused;
    if (fp == NULL)
    {
        return NULL;
    }
    while(fgets(buf, 80, fp))
    {
        if (!strncmp(buf, "cpu", 3))
        {
            sscanf(buf, "%s %d %d %d %d",
                    cpuid,
                    &CpuStat[cidx%2].User,
                    &nused,
                    &CpuStat[cidx%2].System,
                    &CpuStat[cidx%2].Idle
                    );
            break;
        }
    }
    // 처음실행했다면, 1초를 쉰다음 재귀호출 한다.
    if (!cidx)
    {
        sleep(1);
        cidx++;
        *Read();
    }
    cidx++;
    int diff1;
    int diff2;
    int diff3;
    int Idle, System, User;
    diff1 = CpuStat[(cidx+1)%2].User - CpuStat[(cidx)%2].User;
    diff2 = CpuStat[(cidx+1)%2].System - CpuStat[(cidx)%2].System;
    diff3 = CpuStat[(cidx+1)%2].Idle - CpuStat[(cidx)%2].Idle;

    Idle = (diff3*100)/(diff1+diff2+diff3);
    System = (diff2*100)/(diff1+diff2+diff3);
    User = (diff1*100)/(diff1+diff2+diff3);

    sprintf(rtvstr,"CPU=%d,%d,%d\n", User,System,Idle);
    return rtvstr;
}
int Clean()
{
    if (rtvstr != NULL)
        free(rtvstr);
    rtvstr = NULL;
    return 1;
}

+ Recent posts