跳转到主要内容
메인 컨텐츠로 가기

한국 developerWorks  >  리눅스  >

Linux 2.6 속으로!

새로운 커널 연구

developerWorks
문서 옵션

JavaScript가 필요한 문서 옵션은 디스플레이되지 않습니다.


난이도 : 중급

Anand K Santhanam, 소프트웨어 엔지니어, IBM Global Services

2003 년 9 월 23 일
2003 년 12 월 09 일 수정

곧 배포될 리눅스 커널의 신뢰성과 확장성이 더욱 강화됨에 따라 리눅스 선택폭이 넓어질 것이라는 전망을 낳고 있다. 이 글에서 리눅스의 변화된 부분을 조명한다.

리눅스 커널은 1991년 Linus Torvalds가 기본적인 스케줄러와 ipc와 메모리 관리 알고리즘을 포함하여 발표했던 보잘것없는 v 0.1 부터 진화를 거듭했다. 이제는 시장에서 도전적인 위치를 차지하고 있는 대안 OS가 되었다. 게다가 정부와 거대 IT 기업들은 리눅스로 옮겨가고 있다. 리눅스는 이제 최소형 임베디드 장치에서 S/390까지, 손목시계에서 거대한 엔터프라이즈 서버까지 세력을 떨치고 있다.

Linux 2.6은 리눅스 개발 사이클중에서 주요한 차기 버전으로 거론되고 있다. 엔터프라이즈 서버 뿐만 아니라 임베디드 장치 지원에 이르기까지 향상된 퍼포먼스 제공을 목표로하는 강력한 기능을 포함하고 있다.

이 글에서는 열광적인 리눅스 사용자들을 위해 Linux 2.6이의 주요 기능을 분석했다. 드라이버 개발자들이 흥미를 가질만한 다양한 변화를 언급하고 있다.

Linux 2.6 하이라이트

Linux 2.6은 임베디드 시스템 용 리눅스 뿐만 아니라 엔터프라이즈 서버용 리눅스로서도 큰 발전이다. 하이엔드 머신을 위해 새로운 기능들은 퍼포먼스 향상, 확장성, 쓰루풋, SMP 머신에 대한 NUMA 지원을 목표로 한다. 임베디드 세계에서 새로운 아키텍쳐와 프로세서 타입이 추가되었다. 그리고 오디오와 멀티미디어용 드라이버 세트가 추가되었다.

우리는 이 글에서 Linux 2.6의 가장 주목할 만한 기능 몇 가지를 분석한다. 하지만 주목할 만한 변화가 너무 많아서 이곳에 모두 다룰 수는 없었다. 고급 커널 코어 덤핑, 빠른 Mutex 지원, I/O 서브시스템 향상 등이 그것이다. 몇몇 사항들은 사이드바를 참조하고 참고자료의 링크를 참조하기 바란다.




위로


새로운 스케줄러

2.6 Linux 커널은 Ingo Molnar에서 개발한 새로운 스케줄러 알고리즘을 사용한다. O(1) 알고리즘이라고 불리며 부하가 높을 때 뛰어나게 실행되고 많은 프로세서들과 잘 작동된다.

2.4 스케줄러의 경우 타임슬라이스 재계산 알고리즘은 새로운 타임슬라이스가 재계산 되기 전에 모든 프로세스가 그들의 타임슬라이스를 소진해야만 했다. 많은 프로세서들이 있는 시스템에서 대부분의 프로세서들은 프로세스가 그들의 타임슬라이스를 완료하고 새로운 타임슬라이스를 위해 재계산을 기다리는 동안 유휴 상태가된다. 이는 SMP 효율성에 영향을 미친다. 게다가 유휴 프로세서는 타임슬라이스가 미처 소진되지 못한 프로세스를 대기하는 실행을 시작한다. 이는 프로세스가 프로세서들 사이에 바운스(bounce)를 하는 결과를 초래한다. 바운스 프로세스가 높은 우선순위 또는 인터랙티브 프로세스가 될 때, 전체 시스템 퍼포먼스에 영향이 미치게된다.

이 새로운 스케줄러는 CPU 기반 타임슬라이스를 분배하고 글로벌 동기화와 재계산 루프를 제거하여 이러한 문제에 접근한다. 이 스케줄러는 두 개의 우선순위 어레이를 사용한다. 주로 활성 어레이와 종료된 어레이이다. 이들은 포인터를 통해 액세스된다. 활성 어레이는 CPU에 대한 모든 태스크들을 포함하고 있고 타임슬라이스를 남겨둔다. 종료된 어레이는 타임슬라이스가 종료된 모든 태스크의 분류 리스트가 포함되어 있다. 모든 활성 태스크들이 사용되면 어레이에 대한 액세스 포인터는 바뀌고 종료된 어레이는 활성 어레이가 되며 비어있는 활성 어레이는 종료된 태스크를 위한 새로운 어레이가 된다. 이 어레이는 64비트 비트맵으로 저장된다.

새로운 스케줄러에는 큰 runqueue_lock이 더 이상 없다. 프로세서 마다 실행 큐/잠금 메커니즘을 유지하여 두 개의 다른 프로세서 상의 두 개의 프로세스들이 자기, 깨어나기, 콘텍스트 변환 등을 병렬로 완벽히 처리할 수 있도록 한다. 재계산 루프와 goodness 루프는 제거되었고 O(1) 알고리즘은 wakeup()schedule()에 사용된다.

새로운 스케줄러의 효과:

  • SMP 효율성: 실행되어야 할 작업이 있다면 모든 프로세서들은 작동한다.
  • 대기 작동: 오랜 시간동안 어떤 프로세스도 프로세서 타임 없이 머무르지 않는다. 어떤 프로세스도 비합리적으로 많은 CPU 시간을 쓰지 않는다.
  • SMP 친화력: 프로세서들은 CPU 친화력이 있으며 CPU들간 바운스되지 않는다.
  • 우선순위: 덜 중요한 태스크들은 낮은 우선순위로 시작한다.
  • 로드 밸런싱: 스케줄러는 프로세서가 핸들할 수 있는 것 보다 많은 로드를 만들어내는 프로세스의 우선순위를 강등시킨다.
  • 상호작동: 사용자는 마우스 클릭이나 키 탭 작동 후 바로 응답을 받을 수 있다. 심지어 부하가 높을 때도 그렇다.



위로


커널 선점

커널 선점 패치는 2.5 시리즈에 이어 2.6에도 포함되었다. 사용자 인터랙티브 애플리케이션과 멀티미디어 애플리케이션 모두 레이턴시를 현저하게 낮춘다. 이 기능은 실시간 시스템과 임베디드 디바이스에 특히 잘 맞는다.

2.5 커널 선점 작업은 Robert Love가 수행했다. 이전 버전 (2.4 커널 포함)에서는 테스크가 CPU를 스스로 포기하지 않는 한 커널 모드에서는 태스크 실행을 선점할 수 없었다.

2.6 부터 커널은 선점이 가능하다. 커널 태스크는 선점될 수 있고 몇몇 중요한 사용자 애플리케이션이 실행을 지속할 수 있다. 좋은 점은 시스템의 사용자 상호교환성이 증대되고 사용자는 키 스트로크와 마우스 클릭에 보다 빠른 응답을 받을 수 있다.

물론 커널 코드의 모든 부분이 선점될 수 있는것은 아니다. 커널 코드의 특정 중요한 섹션이 선점을 대비해 잠긴다. 잠금은 CPU당 데이터 구조와 상태가 선점으로 부터 언제나 보호된다는 것을 확실히 해두어야 한다.

다음 코드는 CPU당 데이터 구조 문제를 설명하고 있다. (SMP 시스템):


Listing 1. 커널 선점 문제가 있는 코드

  int arr[NR_CPUS];

          arr[smp_processor_id()] = i;
          /* 커널 선점 could happen here */
          j = arr[smp_processor_id()]   /* i and j are not equal as
   smp_processor_id() may not be the same */

이 상황에서 커널 선점이 중요한 시점에 발생한다면 태스크는 재스케줄링 때 다른 프로세서로 할당될 것이다. smp_processor_id()는 다른 값을 리턴한다.

이 상황은 잠금(locking)으로 보호되어야 한다.

커널 선점 FPU 모드는 CPU의 상태가 선점에서 보호되어야하는 곳에서 발생한다. 커널이 플로팅 포인트 명령어를 실행할 때 FPU 상태는 저장되지 않는다. 만약 여기서 선점이 발생하고 재스케쥴되면 FPU 상태는 선점 전의 상태와는 완전 다르게 된다. 따라서 FPU 코드는 커널 선점으로 부터 언제나 잠금 상태가 되어야한다.

잠금은 중요한 섹션에 선점을 못하게 하고 나중에 이를 다시 가능케 함으로서 실행될 수 있다. 다음의 #defines은 2.6 커널에서 사용할 수 있다. 선점을 불가능하게도 가능하게도 한다:

  • preempt_enable() -- 선점 카운터 감소
  • preempt_disable() -- 선점 카운터 증가
  • get_cpu() -- preempt_disable()을 호출한 다음 smp_processor_id()를 호출함
  • put_cpu() -- preemption()을 다시 가능하게 함

이러한 정의를 사용하여 Listing 1은 다음과 같이 재작성 될 수 있다:


Listing 2. 선점에 대비하는 잠금 코드

 int cpu, arr[NR_CPUS];

         arr[get_cpu()] = i;  /* disable preemption */
         j = arr[smp_processor_id()];
         /* do some critical stuff here */
         put_cpu()    /* re-enable preemption */

preempt_disable()/enable() 호출은 중첩되었다는 것에 주목하자. 말하자면 preempt_disable()은 무한대로 호출 될 수 있고 선점은 n번 째 preempt_enable()과 만날 때 재실행 될 것이다.




위로


향상된 쓰레딩 모델과 NPTL 지원

쓰레딩 퍼포먼스를 향상하기 위한 많은 작업들이 2.5 커널에서는 쓰레딩 퍼포먼스를 향상하기 위한 많은 작업들이 이루어졌다. 2.6의 향상된 쓰레딩 모델 또한 Ingo Molnar에 의해 수행되었다. 1:1 쓰레딩 모델에 기반하여 새로운 Native Posix Threading Library (NPTL)의 인커널 지원이 포함되어있다. Ulrich Drepper과 함께 Molnar에 의해 개발되었다.

2.6의 기타 주요 변화

  • 파일시스템
    ext2/ext3 파일시스템이 업그레이드 되었다. 확장 애트리뷰트 지원과 POSIX 액세스 콘트롤 리스트 지원이 추가되었다. NTFS 드라이버는 SMP, 4KB 이상의 클러스터 사이즈 등을 지원할 수 있는 재작성 권한을 가졌다. IBM의JFS (journaling file system)와 SGI의 XFS 모두 2.6에 포함되었다.
  • Audio
    ALSA (Advanced Linux Sound Architecture)라고 알려진 리눅스 오디오 아키텍쳐는 오랫동안 기대하고 있던 것이다. 이것은 많은 한계점을 드러내었던 OSS (Open Sound System) 아키텍쳐를 대체하게 되면서 데스크탑 사용자들에겐 은혜와도 같은 소식이다. 이 새로운 사운드 아키텍쳐는 USB 오디오 및 MIDI 디바이스, 이중 플레이백 등을 지원한다. 전과는 완전 다른 MP3 또는 기타 오디오 파일 실행을 경험 할 수 있다!
  • Bus
    SCSI/IDE 서브시스템들은 재작성 작업이 진행중이고 몇몇 드라이버는 테스팅 또는 클린업 단계에 와있다.
  • 전원 관리
    ACPI (Advanced Configuration and Power Interface) 지원, CPU scaling (전력을 유지하기위해 부하에 변화를 주는 가운데 CPU 클락 주파수를 변화시키는 기능) 지원, 소프트웨어 중지 (테스트중) 등이 지원된다.
  • 네트워킹 & IPSec
    IPSec (IP Security) 지원이 커널에 추가되었다. IP 페이로드 컴프레션 같은 다양한 RFC 지원을 갖게 된 것이다. 인커널(in-kernel) HTTP 서버인 kttpd는 제거되었다. IPSec은 커널에서 제공하는 새로운 암호 API를 사용한다. 이 암호 API에는 MD4, MD5 DES 등의 다양하고 대중적인 알고리즘이 포함되어 있다. NFSv4 (Network File System) 클라이언트/서버도 지원한다.
  • 사용자 인터페이스 레이어
    2.6 커널은 프레임버퍼/콘솔 레이어가 달라졌다. fbset과 fbdesl 같은 사용자 공간의 다양한 프레임버퍼 툴들이 업데이트가 필요하다. 또한 전체적으로 디바이스가 사용자 인터페이스 레이어의 지원을 받을 수 있다.

쓰레딩 실행 속도도 빨라졌다. 2.6 커널은 이제 20억 PID까지 핸들할 수 있다. (IA32).

또 다른 변화는 TLS (Thread Local Storage) 시스템 호출의 도입이다. 쓰레드 레지스터로 사용될 수 있는 GDT (Global Descriptor Table) 엔트리를 한 개 이상 할당 할 수 있다. GDT는 CPU 기반이고 엔트리는 쓰레드 기반이다. 1:1 쓰레딩 모델이 쓰레드의 숫자 제한 없이 만들어 질 수 있다. 2.4 커널은 프로세서 당 최대 8,192 쓰레드만을 허용했다.

클론 시스템 호출이 확대되어 쓰레드 생성을 최적화한다. 이 커널은 CLONE_PARENT_SETID 플래그가 설정되면 주어진 메모리 위치에 쓰레드 ID를 저장하고 CLONE_CLEARID가 설정되면 쓰레드 종료 시 메모리 위치를 지운다. 이는 사용자 레벨의 메모리 관리가 사용되지 않은 메모리 블록을 인식할 수 있도록 돕는다. 또한 쓰레드 레지스터의 신호 보안 로딩 지원이 추가되었다. Futex (fast user space mutex)는 pthread_join 조인에 대해 커널에 의해 수행된다. (참고자료).

POSIX 시그널 핸들링은 커널 공간에서 수행된다. 시그널은 프로세스에서 사용 가능한 것 중 하나에 전달된다. 치명적 시그널은 전체 프로세스를 종료시킨다. 시그널의 정지 및 지속 역시 전체 프로세스에 영향을 준다. 이는 멀티쓰레드 프로세스의 작업 제어를 가능하게 한다.

종료 시스템 호출의 변형이 도입되었다. exit_group()이 호출되면 이 시스템 호출은 전체 프로세스와 쓰레드를 종료한다. 더욱이 종료 핸들링은 O(1) 알고리즘의 도입으로 향상되어 수 십만 개의 쓰레드를 가진 프로세스를 2초에 종료한다.

proc 파일시스템도 변경되어 모든 쓰레드가 아닌 원래 쓰레드만 보고한다. 이는 /proc 리포팅이 느려지는 것을 막는다. 이 커널은 모든 쓰레드가 종료할 때 까지 원래 쓰레드로 남아있다.




위로


VM 변화

VM의 경우, Rik van Riel의 r-map (reverse mapping)이 결합되어 특정 로드 하의 VM 작동 부분에서 주목할만한 향상을 보인다.

역 매핑 기술을 이해하기 위해 리눅스 가상 메모리 시스템의 기초를 알아보자.

리눅스 커널은 가상 메모리 모드에서 작동한다. 모든 가상 페이지의 경우 이에 상응하는 실제 메모리 페이지가 시스템에 존재한다. 가상 페이지와 물리적 페이지 사이의 주소 번역은 하드웨어 페이지 테이블에 의해 수행된다. 특정 가상 페이지의 경우 페이지 테이블 엔트리는 상응하는 물리적 페이지 또는 페이지가 존재하지 않는다는 노트를 준다. 하지만 이 가상-물리적 페이지 매핑은 언제나 1:1 대응이 되는 것은 아니다. 다중의 가상 페이지들은 같은 물리적 페이지를 지목할 수 있다. 공유된 각각의 프로세스 페이지 엔트리는 상응하는 물리적 페이지에 대한 매핑을 갖고있다. 이 같은 경우, 커널에 특정 물리적 페이지를 없는 것으로 (free)하고 싶을 때 상황은 복잡해진다. 물리적 페이지에 대한 레퍼런스를 찾으며 모든 프로세스의 페이지 테이블 엔트리들을 트래버스해야하기 때문이다. 레퍼런스 카운트가 0될 될 때 물리적 페이지를 릴리스 할 수 있다. 커널이 모든 프로세스의 페이지 테이블을 스캔하게 되면서 태스크가 길어질 수 있다. 어떤 프로세스들이 이 페이지에 대한 실제 레퍼런스를 갖고 있는지를 알 방법이 없기 때문이다. 이는 높은 부하에서 VM 속도를 상당히 늦춘다.

역 매핑 패치는 struct 페이지(물리적 페이지 구조)에 pte_chain 이라는 데이터 구조를 도입하여 이 같은 상황을 해결한다. pte_chain은 이 페이지의 PTE를 가르키는 링크 리스트이고 특정 페이지가 레퍼런스 되는 것에서 PTE 리스트를 리턴할 수 있다. page freeing이 매우 쉬워진다.

하지만 이러한 스키마에는 포인터 오버헤드가 있다. struct 페이지는 pte_chain용 여분의 struct를 갖고 있어야한다. 64KB 정도의 물리적 페이지가 있는 256MB 시스템에서 64KB * 의 메모리가 이 목적을 위해 할당되어야 한다.

wait_queue_head_t 필드를 struct 페이지에서 제거하는 것을 포함하여 이러한 문제를 해결하기 위해 많은 기술들이 사용되었다. 대기 큐는 희박하게 사용되기 때문에 해시 큐를 사용하는 보다 작은 큐 구현이 맵 패치에 구현되었다.

rmap 패치의 퍼포먼스(특히 하이엔드 시스템과 높은 부하에서)는 2.4 커널의 VM 시스템 보다 월등히 낫다.




위로


리눅스 2.6으로의 드라이버 포팅

2.6 커널은 드라이버 개발자들을 위해서도 많은 변경 리스트를 마련했다. 이 장에서는 2.4 커널에서 2.6 커널로 포팅하는 장치 드라이버의 중요한 부분을 소개하도록 한다.

우선 이 커널 구현 시스템은 2.4와 비교해 볼 때 더욱 빠른 구현을 제공한다. 향상된 그래픽 툴들도 추가되었다. make xconfig (Qt 라이브러리 필요)와 make gconfig (GTK 라이브러리 필요)가 대표적인 예이다.

2.6 구현 시스템의 핵심은 다음과 같다:

  • make가 발생할 때 arch-zImage와 모듈을 기본적으로 만든다.
  • 병렬 make의 경우, make -jN을 선택하는 것이 좋다.
  • make은 장황하지 않다.
  • make subdir/는 subdir/와 그 밑에 있는 모든 파일을 컴파일한다.
  • make help은 지원되는 make target을 제공한다.
  • make dep은 어떤 단계에서도 실행 될 필요가 없다.

커널 모듈 로더는 2.5에서 완전히 재구현되었다. 모듈 구현 메커니즘이 2.4와는 완전히 다름을 의미한다. 새로운 모듈 유틸리티 세트가 모듈 로딩과 언로딩에 필요하다. 구 2.4 makefile은 2.6에서는 더 이상 작동하지 않는다.

Rusty Russel의 작품이다. 이것은 커널 구현 메커니즘을 사용하여 .o 모듈 객체 대신 .ko (kernel object) 모듈 객체를 만든다. 이 커널 구현 시스템은 모듈을 우선 컴파일하고 그런다음 vermagic.o로 링크된다. 이것은 사용된 컴파일러 버전, 커널 버전, 커널 선점의 사용 여부 같은 정보를 포함하고 있는 객체 모듈에 특별한 섹션을 만든다.

이제 예제를 보고 새로운 커널 구현 시스템이 간단한 모듈을 컴파일하고 로딩하는 방법을 분석해보자. 예제로 사용한 모듈은 "hello world" 모듈이고 module_initmodule_exitinit_modulecleanup_module 대신 사용되었다는 것을 제외하곤 2.4 모듈과 비슷하다. 이 모듈은 hello.c 이고 Makefile은 다음과 같다.


Listing 3. driver makefile

 KERNEL_SRC = /usr/src/linux
           SUBDIR = $(KERNEL_SRC)/drivers/char/hello/
           all: modules

           obj-m := module.o
           hello-objs := hello.o

           EXTRA_FLAGS += -DDEBUG=1

           modules:
                $(MAKE) -C $(KERNEL_SRC) SUBDIR=$(SUBDIR) modules

makefile은 커널 구현 메커니즘을 사용하여 모듈을 구현한다. 컴파일된 모듈은 module.ko라는 이름이 지어지고 hello.c를 컴파일하고 vermagic과 링크하여 얻어진다. KERNEL_SRC는 커널 소스 디렉토리를 나타내고 SUBDIR는 모듈이 위치한 디렉토리를 나타낸다. EXTRA_FLAGS는 컴파일 시간 플래그를 나타낸다.

일단 새로운 모듈(module.ko)이 만들어지면 새로운 모듈 유틸리티를 사용하여 로드/언로드 될 수 있다. 2.4에서 사용된 오래된 모듈 유틸리티는 2.6 커널 모듈의 로딩/언로딩에 사용될 수 없다. 이 새로운 모듈 로딩 유틸리티는 경쟁 조건의 발생을 최소화한다.

2.6에서 모듈은 레퍼런스 카운트를 늘리는(줄이는) 단계를 수행할 필요가 없다. 대신 이제는 모듈 코드 밖에서 수행된다. 모듈을 레퍼런스하는 모든 코드는 try_module_get(&module)을 호출해야 한다. 그리고 성공할 때 모듈에 액세스 할 수 있다. 이 호출은 모듈이 언로드 되면 실패한다. 따라서 이 모듈에 대한 레퍼런스는 module_put()을 사용하여 릴리스 될 수 있다.




위로


메모리 관리 변화

메모리 풀은 2.5 개발 중 추가되어 휴면 상태 없이 메모리 할당을 만족시킨다. 이 개념에는 메모리 풀을 사전할당하고 실제 필요할 때 까지 이를 보유하는 것이 포함되었다. mempool은 mempool_create() 호출을 사용하여 만들어진다:

mempool_t *mempool_create(int min_nr, mempool_alloc_t *alloc_fn,
mempool_free_t *free_fn, void *pool_data);

min_nr는 필요한 사전 할당된 객체의 수이고 alloc_fnfree_fn은 표준 객체 할당/할당 해제 루틴에 대한 포인터이다:

typedef void *(mempool_alloc_t)(int gfp_mask, void *pool_data);
typedef void (mempool_free_t)(void *element, void *pool_data);

pool_data는 할당/할당 해제 함수에 의해 사용되는 포인터이고 gfp_mask는 할당 플래그이다. 할당 함수는 __GFP_WAIT 플래그가 지정되지 않는한 휴지상태(sleep) 상태가 되지 않는다.

풀 안에 있는 할당과 할당 해제 객체는 다음에 의해 수행된다:

void *mempool_alloc(mempool_t *pool, int gfp_mask);
void mempool_free(void *element, mempool_t *pool);

mempool_alloc()은 객체 할당을 위한 것이다. mempool 할당자가 메모리 제공에 실패하면 사전 할당 풀이 사용된다.

mempool은 mempool_destroy()를 사용하여 시스템에 리턴된다.

메모리 할당을 위해 mempool 기능을 도입하는 것 외에도 2.5 커널은 세 가지의 새로운 GFP 플래그를 일반적인 메모리 할당에 도입했다. 다음과 같다:

  • __GFP_REPEAT
  • __GFP_NOFAIL
  • __GFP_NORETRY

메모리 할당 변화와는 별도로 remap_page_range () 호출도 약간 변경되었다. 2.4와 비교하여 추가 매개변수가 쓰인다. 가상 메모리 구역(VMA) 포인터는 첫 번째 매개변수로서 추가되어야 하며 기존의 네 개의 매개변수(시작, 끝, 크기, 보호 플래그)가 뒤따른다.




위로


Workqueue 인터페이스

Workqueue 인터페이스는 2.5 개발에 도입되어 태스크 큐 인터페이스를 대체한다. 각각의 Workqueue는 이것과 관련된 워커 쓰레드에 쓰이고 실행 큐의 모든 태스크들은 프로세스 개념으로 실행된다. 드라이버는 고유의 Workqueue를 만들고 사용할 수 있고 또는 커널에서 제공하는 것을 사용한다. Workqueue는 다음을 사용하여 만들어진다:

struct workqueue_struct *create_workqueue(const char *name);

name은 Workqueue의 이름이다.

Workqueue 태스크는 컴파일 또는 실행 시간에 초기화 될 수 있다. 이 태스크는 work_struct 구조안으로 패키지되어야 한다. Workqueue 태스크는 컴파일 시간동안 다음을 사용하여 초기화된다:

DECLARE_WORK(name, void (*function)(void *), void *data);

name은 work_struct 이름이고 function은 태스크가 스케쥴링 될 때 호출되는 함수이며 data는 이 함수에 대한 포인터이다.

Workqueue 태스크는 런타임 동안 다음을 사용하여 초기화된다:

INIT_WORK(struct work_struct *work, void (*function)(void *), void *data);

작업을 Workqueue로 큐잉하는 것은 다음 함수 호출을 통해 수행된다:

int queue_work(struct workqueue_struct *queue, struct work_struct *work);
int queue_delayed_work(struct workqueue_struct *queue, struct work_struct
*work, unsigned long delay);

queue_delayed_work()의 지연은 jiffies에서의 최소한의 지연이 Workqueue 엔트리가 실제로 실행을 시작하기 전에 주어진다는 것을 확인하기 위함이다.

Workqueue에 있는 엔트리들은 불특정 시간 또는 지연 시간 후에 관련 워커 쓰레드에 의해 실행된다. 평상시보다 긴 실행 시간이 걸리는 Workqueue 엔트리는 다음을 사용하여 취소된다:

int cancel_delayed_work(struct work_struct *work);

취소 호출이 리턴될 때 엔트리가 실제로 실행되면 엔트리는 실행을 계속하지만 큐에 다시는 추가되지 않는다. Workqueue는 다음을 사용하여 엔트리를 플러시(flush)한다:

void flush_workqueue(struct workqueue_struct *queue);

다음을 사용하여 파괴된다:

void destroy_workqueue(struct workqueue_struct *queue);

모든 드라이버들이 고유의 커스텀 Workqueue를 가질 필요는 없다. 드라이버들은 커널에서 제공되는 디폴트 Workqueue를 사용할 수 있다. 이 Workqueue는 많은 드라이버와 공유되기 때문에 엔트리 실행에 오랜 시간이 걸린다. 이를 해결하려면 워커 함수에서의 지연이 최소로 유지되어야하고 또는 지연을 피해야한다.

디폴트 큐를 모든 드라이버에서 사용할 수 있지만 GPL 라이센스의 드라이버만이 커스텀 정의의 Workqueue를 사용할 수 있다는 것을 기억하라:

  • int schedule_work(struct work_struct *work); -- 엔트리를 Workqueue에 추가한다.
  • int schedule_delayed_work(struct work_struct *work, unsigned long delay); -- Workqueue 엔트리를 추가하고 실행을 지연시킨다.

flush_scheduled_work() 함수가 있는데 이것은 큐에 있는 모든 것이 실행될 때를 기다리면서 언로딩 시 모듈에 의해 호출되어야한다.




위로


인터럽트 루틴 변화

2.5 인터럽트 핸들러 내부는 많은 변경을 수행중이다. 하지만 이중 대부분은 드라이버 개발자와는 관련이 없다. 하지만 디바이스 드라이버에 영향을 줄 수 있는 중요한 변화가 몇몇 있다.

인터럽트 핸들러 함수는 irqreturn_t 유형의 리턴 코드를 갖고 있다. Linus에 의해 도입된 이 변화는 인터럽트 핸들러가 기본적인 IRQ 레이어에게 인터럽트가 적용되는지의 여부를 말해준다. 이는 인터럽트가 계속해서 들어오고 어떤 드라이버도 이에 대해 어떤 것도 수행할 수 없는 곳에서 가짜 인터럽트를 잡는다. 2.6에서 이 드라이버는 인터럽트가 그 디바이스에서 나온다면 IRQ_HANDLED를 리턴해야하 관련없는 것이 나오면 IRQ_NONE를 리턴한다. 커널 IRQ 레이어는 어떤 드라이버가 특정 인터럽트를 핸들링하는지를 구별한다. 인터럽트가 지속적으로 들어오고 이에 이 디바이스에 대한 등록된 핸들러가 없다면 커널은 이 디바이스에서 인터럽트를 막을 수 있다. 기본적으로 드라이버 IRQ 루틴은 IRQ_HANDLED를 리턴해야한다. 드라이버가 그 인터럽트를 실제로 핸들할 때 IRQ_NONE을 리턴하는 버그이기 때문이다. 이 새로운 인터럽트 핸들러는 다음과 같다:


Listing 4. 2.6 인터럽트 핸들러 가짜 코드

  irqreturn_t irq_handler(...) {
                         ..
                         if (!(my_interrupt)
                                     return IRQ_NONE;  // not our interrupt
                         ...
                         return IRQ_HANDLED;  // return by default
             }




위로


통합된 디바이스 모델

2.5에서 가장 주목할 만한 변화중 하나는 통합 디바이스 모델의 탄생이다. 이 디바이스 모델은 전체 디바이스 아키텍쳐를 나타내고 많은 데이터 구조를 유지하여 시스템의 오버레이를 나타낸다. 디바이스 전체적으로 전원 관리 제어가 향상되었고 디바이스 관련 태스크 관리가 쉬워졌다. 다음 사항을 트래킹하는 것도 포함되어있다:

  • 시스템에 존재하는 디바이스와 이것이 연결되어 있는 버스
  • 특정 상황에서의 디바이스 전원 상태
  • 시스템에 알려진 디바이스 드라이버와 이들이 제어하는 디바이스
  • 시스템의 버스 구조: 버스-디바이스 연결 관계와 버스 간 상호 연결 관계
  • 시스템에 나타난 디바이스 클래스

디바이스 드라이버와 관련한 2.5 커널 내용:

  • malloc.h이 제거되었다. <linux/malloc.h>를 포함하는 모든 코드는 이제 <linux/slab.h>를 사용해야 한다.
  • HZ 값이 x86 아키텍쳐를 위해 1000으로 증가했다.
  • ndelay()라는 새로운 지연 함수가 도입되었다.
  • seqlock()이라는 새로운 유형의 LOCK이 자주 접근되는 데이터의 작은 부분을 잠그는데 도입되었다.
  • 2.6 커널은 선점될 수 있다.
  • 비동기식 I/O가 2.5에 추가되었다.
  • 블록 레이어가 2.5 에서 변경되었다.
  • sysfs가 도입되었다.



위로


참고자료




위로


필자소개

Anand K. Santhanam: 소프트웨어 엔지니어, IBM Global Services.





위로


기사에 대한 평가

매우 불만족 (1)
불만족 (2)
보통 (3)
만족 (4)
매우 만족 (5)




위로



    IBM 소개개인정보 보호정책문의