본문 바로가기
오픈소스 읽기 (OLD)/모던 IPC 시스템 - D-Bus

2. D-Bus 사용방법

by 커널패닉 2021. 4. 11.
반응형

www.kernelpanic.kr/category/%EC%98%A4%ED%94%88%EC%86%8C%EC%8A%A4%20%EC%9D%BD%EA%B8%B0/%EB%AA%A8%EB%8D%98%20IPC%20%EC%8B%9C%EC%8A%A4%ED%85%9C%20-%20D-Bus

 

'오픈소스 읽기/모던 IPC 시스템 - D-Bus' 카테고리의 글 목록

 

www.kernelpanic.kr

 

2.1 D-Bus 라이브러리들

어플리케이션 입장에서는 D-Bus는 사용 규약만 지킨다면, 언어 / 라이브러리와 무관하게 동일한 동작을 수행한다. 따라서 D-Bus는 다양한 언어로 구현이 되어 있으며, 동일한 언어에서도 다양한 라이브러리들이 있다. 대표적으로 D-Bus를 사용하기 위한 라이브러리들은 다음과 같다.

라이브러리 지원 언어 특징
libdbus c low-level의 dbus api. 랭기지 바인딩이나, dbus-daemon을 제작하기 위한 목적의 라이브러리로, 어플리케이션 개발 용도로는 부적절함.
sd-bus c systemd에서 제공하는 라이브러리. 장점: 사용하기 간편하고, 속도가 빠름. 단점: 레퍼런스가 부족함
gdbus c gtk 계열의 dbus 라이브러리. 장점: 사용하기 편하고, 다양한 기능들을 제공. gdbus-codegen을 통해서, Interface를 바탕으로 자동으로 스켈레톤 코드를 생성. 단점: gobject에 대한 개념을 알고 있어야 함.
pydbus python 파이썬 답게 아주 간결한 사용 방법을 제공.
dbus(rust) rust tokio 지원 등 rust 시스템에 잘 녹아든 dbus 라이브러리. rust를 위한 dbus-codgen을 제공.

물론 java, go 등에도 D-Bus API들이 있다. 구글링을 해 보니 Java는 dbus-java, go는 gobus가 상위권에 검색이 된다. 다만 본인이 해당 언어를 주로 사용하지 않아서, 해당 언어들에 대해서는 각자 좀더 서치해보자.

본 포스트에서는 sd-bus를 이용해서 D-Bus 서버 / 클라이언트를 제작하는 방법에 대해서 다룰 예정이다. pydbus나 dbus(rust)를 사용하지 않은 이유는, dbus에 대한 개념이 있다면 금방 API를 이해해서 사용할 수 있고, 해당 언어들이 c에 비해서 덜 알려져 있기 때문이다. (python의 경우 논란이 있을 수 있지만, 임베디드 / 리눅스 분야에서는 아무래도 c가 더 보편적이지 않을까..?) libdbus는 애초에 일반적인 어플리케이션에 사용하기 위한 목적으로 있는 라이브러리가 아니고, gdbus는 개인적으로 선호하기는 하지만 gobject에 대한 개념이 선행되어야 하기 때문에 다루지 않았다.

 

2.2 D-Bus 서버 작성

D-Bus 서버 프로그램은 다음과 같은 과정을 갖는다.

  1. D-Bus 데몬(dbus-daemon)에 연결 (system 또는 session)
  2. Bus 이름 등록
  3. Path와 Interface 등록

아래 코드는 기본적인 기능을 제공하는 D-Bus 서버 프로그램이다. 

#include <stdio.h>
#include <stdlib.h>
#include <systemd/sd-bus.h>

static int say_hello_world(sd_bus_message *m, void *userdata, sd_bus_error *error) {
    const char *hello;
    const char *world;
    const int bufsize = 32;
    char helloworld[bufsize];
    int ret = 0;

    ret = sd_bus_message_read(m, "ss", &hello, &world);
    if (ret < 0) {
        fprintf(stderr, "Failed to parse arguments\n");
        return ret;
    }

    strncpy(helloworld, hello, bufsize);
    strncat(helloworld, world, bufsize);

    return sd_bus_reply_method_return(m, "s", helloworld);
}

static int say_this_year_month_day(sd_bus_message *m, void *userdata, sd_bus_error *error) {
    int year;
    unsigned int month;
    unsigned int day;
    const int bufsize = 32;
    char date[bufsize];
    int ret = 0;

    ret = sd_bus_message_read(m, "iuu", &year, &month, &day);
    if (ret < 0) {
        fprintf(stderr, "Failed to parse arguments\n");
        return ret;
    }

    snprintf(date, bufsize, "%d-%u-%u", year, month, day);

    return sd_bus_reply_method_return(m, "s", date);
}

/*
 * 인터페이스를 만드는 매크로
 * Type을 결정짓는 인자는 https://pythonhosted.org/txdbus/dbus_overview.html의 Signature Strings 항목 참조
 * 기본적인 사용 방법은 https://www.freedesktop.org/software/systemd/man/sd_bus_add_object.html 참조
 */
static const sd_bus_vtable dbusexmaple_vtable[] = {
    SD_BUS_VTABLE_START(0),                                 /* 구조체 시작 매크로 */
    SD_BUS_METHOD_WITH_ARGS(                                /* method 인터페이스 */
            "SayHelloWorld",                                /* method 이름 */
            SD_BUS_ARGS("s", hello, "s", world),            /* 인자들(ex. "s"=string) */
            SD_BUS_RESULT("s", helloworld),                 /* 결과값 */
            say_hello_world,                                /* 처리를 위한 콜백 함수 */
            SD_BUS_VTABLE_UNPRIVILEGED),                    /* interface를 공개(introstectable)하도록 플래그 설정 */
    SD_BUS_METHOD_WITH_ARGS(
            "SayThisYearMonthDay", 
            SD_BUS_ARGS("i", year, "u", month, "u", day),
            SD_BUS_RESULT("s", date),
            say_this_year_month_day, 
            SD_BUS_VTABLE_UNPRIVILEGED),
    SD_BUS_VTABLE_END                                       /* 구조체 종료 매크로 */
};

int main() {
    sd_bus_slot *slot = NULL;
    sd_bus *bus = NULL;
    int ret = 0;

    /*
     * session 버스에 연결
     * 참고: https://www.freedesktop.org/software/systemd/man/sd_bus_default.html
     */
    ret = sd_bus_default_user(&bus);
    if (ret < 0) {
        fprintf(stderr, "Failed to connect to dbus-daemon\n");
        goto finish;
    }

    /*
     * Bus Name 요청
     * 참고: https://www.freedesktop.org/software/systemd/man/sd_bus_request_name.html
     */
    ret = sd_bus_request_name(bus, "kr.kernelpanic.Example", 0);
    if (ret < 0) {
        fprintf(stderr, "Failed to acquire name\n");
        goto finish;
    }

    /*
     * Object Path와 Interface 등록
     * 참조: https://www.freedesktop.org/software/systemd/man/sd_bus_add_object.html
     */
    ret = sd_bus_add_object_vtable(bus,
            &slot,
            "/kr/kernelpanic/Example",      /* object path */
            "kr.kernelpanic.DBusExample",   /* interface name */
            dbusexmaple_vtable,
            NULL);
    if (ret < 0) {
        fprintf(stderr, "Failed to add object interface\n");
        goto finish;
    }

    for (;;) {
        ret = sd_bus_process(bus, NULL);
        if (ret < 0) {
            fprintf(stderr, "Failed to process bus\n");
            goto finish;
        }else if (ret > 0) {
            /* 
             * 만약 sd_bus_process에서 처리가 발생되면, 
             * wait을 하지 않고 다음 입력 처리
             */
            continue;
        }

        ret = sd_bus_wait(bus, (uint64_t)-1);
        if (ret < 0) {
            fprintf(stderr, "Failed to wait on bus\n");
            goto finish;
        }
    }

finish:
    sd_bus_slot_unref(slot);
    sd_bus_unref(bus);

    return 0;
}

컴파일

$ gcc server.c -o server `pkg-config --cflags --libs libsystemd`
$ ./server

동작 테스트

$ busctl --user call kr.kernelpanic.Example /kr/kernelpanic/Example kr.kernelpanic.DBusExample SayHelloWorld ss "Hello" "World"
$ busctl --user call kr.kernelpanic.Example /kr/kernelpanic/Example kr.kernelpanic.DBusExample SayThisYearMonthDay iuu 2021 04 12

 

2.3 D-Bus 클라이언트 작성

D-Bus 클라이언트 프로그램은 서버에 대해 요청을 보내고, 응답을 받을 수 있다. 아래 C 코드는 위에서 busctl과 동일한 동작을 수행한다.

#include <stdio.h>
#include <stdlib.h>
#include <systemd/sd-bus.h>

int main() {
    sd_bus_error error = SD_BUS_ERROR_NULL;
    sd_bus_message *m = NULL;
    sd_bus *bus = NULL;
    const char *helloworld;
    const char *this_ymd;
    int ret;

    /*
     * session 버스에 연결
     * 참고: https://www.freedesktop.org/software/systemd/man/sd_bus_default.html
     */
    ret = sd_bus_default_user(&bus);
    if (ret < 0) {
        fprintf(stderr, "Failed to connect to dbus-daemon\n");
        goto finish;
    }

    /*
     * D-Bus 메시지 호출
     * 참고: https://man7.org/linux/man-pages/man3/sd_bus_call_method.3.html
     */
    ret = sd_bus_call_method(bus,
            "kr.kernelpanic.Example",           /* bus name */
            "/kr/kernelpanic/Example",          /* object path */
            "kr.kernelpanic.DBusExample",       /* interface name */
            "SayHelloWorld",                    /* method name */
            &error,                             /* 실패 시 에러메시지 */
            &m,                                 /* 성공 시 메시지 */
            "ss",                               /* 인자형식(string, string) */
            "Hello", "World");                  /* 인자들 */
    if (ret < 0) {
        fprintf(stderr, "Failed to issue method call\n");
        goto finish;
    }

    /*
     * 성공 메시지를 읽어서 return 값 획득
     * 참고: https://www.freedesktop.org/software/systemd/man/sd_bus_message_read.html
     */
    ret = sd_bus_message_read(m, "s", &helloworld);
    if (ret < 0) {
        fprintf(stderr, "Failed to parse response message\n");
        goto finish;
    }

    printf("SayHelloWorld: %s\n", helloworld);

    ret = sd_bus_call_method(bus,
            "kr.kernelpanic.Example",
            "/kr/kernelpanic/Example",
            "kr.kernelpanic.DBusExample",
            "SayThisYearMonthDay",
            &error,
            &m,
            "iuu",                              /* 인자형식(int, uint, uint) */
            2020, 04, 12);                      /* 인자들 */
    if (ret < 0) {
        fprintf(stderr, "Failed to issue method call\n");
        goto finish;
    }

    ret = sd_bus_message_read(m, "s", &this_ymd);
    if (ret < 0) {
        fprintf(stderr, "Failed to parse response message\n");
        goto finish;
    }

    printf("SayThisYearMonthDay: %s\n", this_ymd);

finish:
    sd_bus_error_free(&error);
    sd_bus_message_unref(m);
    sd_bus_unref(bus);

    return 0;
}

컴파일

$ gcc client.c -o client `pkg-config --cflags --libs libsystemd`
$ ./client

 

반응형