'오픈소스 읽기/모던 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 서버 프로그램은 다음과 같은 과정을 갖는다.
- D-Bus 데몬(dbus-daemon)에 연결 (system 또는 session)
- Bus 이름 등록
- 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
'오픈소스 읽기 (OLD) > 모던 IPC 시스템 - D-Bus' 카테고리의 다른 글
번외. 주요 D-Bus 프로토콜 스펙들 (0) | 2021.04.13 |
---|---|
1. D-Bus 기초 (2) | 2021.03.27 |
0. 전통적인 리눅스/Unix IPC 방법들 (0) | 2021.03.22 |