본문 바로가기
SW/C,C++

[C/C++] 커널 드라이버에서의 함수포인터

by FastBench 2024. 4. 22.

커널 드라이버에서의 함수포인터


구조체에 함수 포인터를 사용하는 방법은 커널 드라이버나 다른 시스템 수준의 프로그래밍에서 매우 유용하게 사용된다. 이러한 방식은 다양한 기능을 동적으로 처리할 수 있게 해주며, 특히 장치 드라이버의 인터페이스를 관리할 때 흔히 볼 수 있다.

 

커널 드라이버에서의 사용

커널 드라이버에서는 구조체와 함수포인터를 사용하여 다양한 하드웨어 또는 가상 장치의 동작을 추상화한다.
예를 들어, Linux의 파일 시스템 또는 네트워크 장치 드라이버에서는 각각의 장치 특성에 맞게 동작을 정의할 수 있는 함수 포인터를 포함하는 구조체를 사용한다.

 

이러한 방식은 각 장치 또는 드라이버가 동일한 인터페이스를 공유하면서도, 내부적으로 다른 구현을 가질 수 있게 해주어 코드의 재사용성과 모듈성을 높인다.

 

커널 드라이버와 플러그인 아키텍처

커널 드라이버에서의 함수 포인터 사용은 플러그인 아키텍처의 구현을 가능하게 한다:

  1. 동적 연결: 각 드라이버 모듈은 특정한 장치 또는 기능을 대상으로 하는 함수들을 포함한다. 이 함수들은 드라이버 구조체 내의 함수 포인터를 통해 연결된다. 이렇게 함으로써, 커널은 실행 중에 다양한 드라이버 모듈을 로드하고, 각 모듈이 제공하는 기능을 사용할 수 있다.
  2. 추상화된 인터페이스: 드라이버 구조체는 일반적으로 추상화된 인터페이스를 제공한다. 예를 들어, 모든 네트워크 카드 드라이버는 데이터를 송수신하는 기본 함수를 제공할 수 있으며, 이는 함수 포인터를 통해 구현된다. 각 드라이버는 이 인터페이스에 맞춰 구현되므로, 다양한 네트워크 카드가 동일한 방식으로 제어될 수 있다.
  3. 확장성: 새로운 하드웨어 지원을 추가하기 위해서는 새 드라이버 모듈만 개발하면 된다. 기존 시스템을 수정할 필요 없이 새 모듈을 로딩함으로써, 시스템은 새 하드웨어의 기능을 바로 사용할 수 있게 된다.

예시

먼저 device 구조체가 존재하고, 해당 device 의 동작을 정의하는 device_operation 구조체가 존재한다.

device_operation 에 들어가는 함수들은, 각 device / driver 제조사 별로 구현이 다르기 때문에, 
커널입장에서는 공통된 인터페이스를 제공해야 하므로 함수포인터로 선언한다.

#include <stdio.h>

typedef struct device {
    const char* name;
    int status;
} device;

// Device operation 구조체
typedef struct device_operations {
    int (*open)(device *dev);
    int (*close)(device *dev);
    int (*read)(device *dev, char *buffer, size_t size);
} device_operations;

그리고 아래와 같이 operation 함수들을 정의해준다.

// 각 함수 구현
int device_open(device *dev) {
    printf("Opening device %s\n", dev->name);
    dev->status = 1;
    return 0; // 성공
}

int device_close(device *dev) {
    printf("Closing device %s\n", dev->name);
    dev->status = 0;
    return 0; // 성공
}

int device_read(device *dev, char *buffer, size_t size) {
    printf("Reading from device %s\n", dev->name);
    // 예시 데이터
    snprintf(buffer, size, "Sample data from %s", dev->name);
    return 0; // 성공
}

snprintf 는 buffer 에 문자열을 버퍼오버플로우없이 안전하게 저장하도록 하는 표준 함수이다.

 

main 함수에서 디바이스 구조체를 초기화 해주고, 함수를 함수포인터에 연결해준다.
실제 이 과정은 커널드라이버내의 초기화 함수에서 이뤄질 것이다.

int main() {
    device myDevice = {"Device1", 0};
    device_operations myDeviceOps = {
        .open = device_open,
        .close = device_close,
        .read = device_read,
    };

    // 장치 사용 예제
    myDeviceOps.open(&myDevice);
    char buffer[100];
    myDeviceOps.read(&myDevice, buffer, sizeof(buffer));
    printf("Processed data : %s\n",buffer);
    myDeviceOps.close(&myDevice);

    return 0;
}

결과

 

이렇게 간단한  디바이스 드라이버 예시로부터 구조체 함수포인터가 사용되는 것을 살펴보았는데,

이전 게시글에서는 함수포인터를 '콜백함수'의 용도에 집중해서 설명했다면

 

이번 게시글에서는 함수포인터의 용도 중 하나인 플러그인 아키텍처의 구현을 위주로 설명했다.

 

이러한 구조는 특히 디바이스 드라이버 개발에서 다양한 하드웨어 또는 소프트웨어 컴포넌트의 기능을 동적으로 로딩하고 실행할 수 있도록 한다.

댓글