DBus

Linux/Linux 일반 : 2018. 3. 27. 14:20
반응형

출처 : https://www.joinc.co.kr/w/man/12/DBus

출처 : https://www.joinc.co.kr/w/Site/system_programing/IPC/dbus

출처 : https://makersweb.net/linux/2027


1. DBus

D-Bus는 message bus 시스템으로 애플리케이션간 통신을 위한 기능들을 지원한다. IPC(Inter process communication) 일종이라 할 수 있겠다. DBus는 IPC의 기능이외에 프로세스의 lifecycle를 관리하는 기능도 가지고 있다. Single instance 애플리케이션이나 데몬 프로세스를 관리하거나 서비스가 필요한 애플리케이션을 실행해 주는 등의 작업을 지원한다는 건데, 직접 테스트를 해봐야 겠다. 오늘은 일단 DBUS 서비스와 클라이언트를 만드는 것에 집중한다.

DBus는 "새로운 하드웨어 추가"나 "프린터 큐 변경 확인"과 같은 이벤트 처리를 위한 시스템 데몬과 애플리케이션간 통신을 위한 전통적인 IPC 기능 모두를 지원한다. 리눅스에서 널리 사용되고 있으며, 특히 KDE와 Gnome같은 데스크탑 환경에서 중요하게 사용한다. 데스크탑 환경의 경우 다양한 컴포넌트들을 서로 통합할 수 있어야 하는데, DBus가 이런 역할을 수행한다.

D-Bus는 C#, Java, Ruby, Python, Go와 같은 다양한 언어들의 구현체가 있다. 나는 Ruby를 이용해서 DBus를 테스트 할 계획이다.

1.1. 최종 목적

DBus를 이용해서 Avahi의 기능을 이용하는게 목적이다. 시스템 프로그래밍에서 손을 뗀게 벌써 2년은 된 것 같은데, 이제와서 Avahi를 이용하기 위해서 DBus를 배우는 것은 귀찮고 해서 그냥 DNS-SD를 직접 이용 zero conf 환경을 만들어볼 생각이었다. 그래 DNS-SD 모듈을 이용해서 테스트 프로그램을 만들어서 돌렸더니 아주 친절하게도 "Avahi 응용을 만들고 싶으면, DNS-SD보다는 Avahi를 사용하는게 정신건강에 좋을 겁니다. Avahi는 Bonjour도 잘 지원하고 있거든요"라는 메시지를 출력해주는 거다. 그래서 눈물을 머금고 DBus를 공부하게 됐다.

1.2. 테스트 및 개발 환경

  • 우분투 리눅스 14.04
  • Ruby 2.1.2

2. DBus 개요

2.1. DBus 아케텍처

http://dbus.freedesktop.org/doc/diagram.png

그림은 복잡해 보이지만 내용은 단순하다.

  • Bus Daemon Process는 메시지의 경로를 설정하기 위한 Destination Table를 가지고 있다.
  • 메시지는 브로드케스팅 하는 signal 메시지와 목적지에 전송하는 메시지 두 개의 타입이 있다.
  • Application Process와 Bus Daemon Process는 DBusConnection Instance를 이용해서 서로 통신한다.
  • 통신에는 소켓을 사용한다.

원격에 있는 프로세스와 통신하기 위해서 필요한게 무언지를 생각하면 된다. 1. 프로세스의 이름을 알아낸 다음 2. 프로세스에서 제공하는 기능을 호출하고 3. 때때로 비동기적인 이벤트를 받는다. 이들 3가지 구성요소만 있으면, 원격 프로세스와 통신할 수 있다. 이 3가지 기본 구성요소에, 이름관리 규칙, marshalling & unmarshalling를 위한 여러 기능들을 추가해서 DBus 시스템이 완성된다.

2.2. Object Path

데이터 통신을 하기 위해서는 네트워크 상에서 나와 상대방의 위치를 특정할 수 있어야 한다. 인터넷에서 나와 상대방의 컴퓨터를 찾기 위해서 이더넷 카드에 IP 주소를 할당하는 것을 생각해보라.

DBus도 통신 서비스를 제공하는 시스템이기 때문에, 자신에 연결한 애플리케이션의 경로를 설정하기 위한 경로 식별자(identified)가 필요하다. 이 경로 식별자를 object path라고 한다. Object path는 표준 유닉스 파일 시스템 경로 형식을 사용한다. 유닉스 파일 시스템 경로와 다른 점이라면 숫자, 문자, 밑줄, / 만 사용할 수 있다는 점이다.

2.3. Interface

DBus 인터페이스는 DBus객체를 호출하기 위한 "메서드"와 "시그널"이다. 사용자는 "인터페이스"를 이용해서, 메서드와 시그널을 호출할 수 있다. 인터페이스는 하나 이상의 메서드와 시그널을 가질 수 있다. 예컨데, 인터페이스는 메서드와 시그널을 묶어주는(혹은 연결해주는) 그룹 정도로 볼 수 있을 것이다.

인터페이스는 충돌 가능성을 줄이기 위해서 DNS 도메인 형식을 (뒤집어서)사용한다. 예를들어 avahi의 경우 "org.freedestop.Avahi"와 같은 인터페이스 이름을 가진다. JMusic이라는 음악 애플리케이션을 만든다고 가정해보자. JMusic의 관리 기능을 위해셔서 "com.joinc.JMusic.Manager"이름을 가지는 인터페이스를 만들 수 있을 것이다. 이 인터페이스는 "목록보기", "목록추가", "삭제"와 같은 method들을 가지고 있을 것다. 이들은 대략 아래처럼 네이밍 할 수 있을 것이다.

  • com.joinc.JMusic.Manager.GetAllList
  • com.joinc.JMusic.Manager.AddList
  • com.joinc.JMusic.Manager.DelList

인터페이스는 시그널을 가질 수도 있는데, JMusic의 경우 "volume up/down", stop, start, pause 등의 시그널을 가진다. 제어와 관련된 시그널이니, 인터페이스의 이름은 Control로 하기로 했다.

  • com.joinc.JMusic.Control.volume_up
  • com.joinc.JMusic.Control.volume_down
  • com.joinc.JMusic.Control.start

2.4. Signature Strings

메서드와 시그널은 "매개변수"를 필요로 하는 경우가 있다. 이 경우 매개변수의 타입을 정의를 해야 한다. DBus는 Signatures라고 부르는 string 기반의 인코딩 매커니즘을 지원하는데, 이걸 이용해서 매개변수의 타입을 설정할 수 있다. 아래는 Signature 인코딩에 사용하는 문자와 문자가 의미하는 데이터 타입을 정리한 표다.

문자데이터 타입
y8-bit unsigned integer
b불리언 타입
n16-bit signed integer
q16-bit unsigned integer
i32-bit sined integer
u32-bit unsined integer
x64-bit sined integer
t64-bit unsined integer
ddouble-precision floating point
sUTF-8 string
oD-Bus Object Path string
gD-Bus Signature string
aarray
(Strucure 시작
)Structure 끝
vVariant Type
{Dictionary/Map 시작
}Dictionary/Map 끝
hUnix file descriptor

2.5. DBus Client

DBus 클라이언트는 DBus에 연결하는 모든 프로세스다. 클라이언트들은 버스에 시그널과 메서드를 등록하거나 원격에 있는 메서드를 호출하고 (시그널)이벤트를 받는 식으로 클라이언트간 메시지를 교환한다.

2.6. 인터페이스 정의

D-Bus의 인터페이스는 일반적으로 응용 프로그램이 제공하는 클래스의 API에 대응되며, XML 언어로 기술할 수 있다. 앞서 사용했던 JMusic을 DBus XML문서로 기술했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<node>
<interface name="com.joinc.JMusic.Manager">
<method name="GetAllList">
</method>
<method name="AddList">
<arg name="filename" type="s" direction="in"/>
</method>
<method name="DelList">
<arg name="filename" type="s" direction="in"/>
</method>
</interface>
<interface name="com.joinc.JMusic.Control">
<signal name="start">
<arg name="state" type="i"/>
<arg name="error" type="s"/>
</signal>
</interface>
</node>
  • DBus 객체는 하나 이상의 인터페이스를 가질 수 있다.
  • 인터페이스는 하나 이상의 메서드를 가질 수 있다.
  • 메서드는 매개변수를 가질 수 있다. 메서드는 옵션으로 "name"을 가질 수 있다.
  • 메서드는 반환 값을 가질 수 있다.
  • direction은 입출력을 결정하기 위해서 사용한다. in이면 입력, out이면 출력이다. AddList, delMethod의 경우는 삭제할 파일을 입력해야 하니, direction은 in이 된다. signal의 경우에는 입력이 없고 출력(out)만 있다. 즉 "start" 시그널의 경우에 실행 결과로 state와 error를 수신한다.

3. 서비스 개발

DBus 기반으로 통신하는 음악 서비스 서버와 음악 클라이언트 애플리케이션을 만들어 보려고 한다. 음악 서비스의 이름은 "jmusic"이다. jmusic 서비스의 기능은 아래와 같다.

  • 기능 카테고리는 "Manager", "Player" 두 개로 나눈다.
  • Manager는 곡을 관리하는 기능을 가지고 있다.
    • add : 재생 배열(array)에 음악을 추가한다.
    • getList : 재생 배열에 있는 음악 목록을 출력한다.
  • Palyer는 음악 재생관련 기능을 가지고 있다. play와 playAll 두 개의 기능을 가진다.
    • play : 음악 하나를 선택해서 재생한다.
    • playAll : 목록에 있는 모든 음악을 차례대로 재생한다.
  • playAll로 모든 곡을 재생 할 경우 시그널을 사용할 수 있다.
    • MusicStart signal : 새로운 음악이 시작했음을 알려준다.
    • MusicEnding signal : 음악 재생이 끝났음을 알려준다.

3.1. gem 목록들

dbus와 getopt 모듈을 설치한다.

1
2
# gem install ruby-dbus
# gem install getopt

3.2. jmusic 서비스 서버

설명은 주석으로 대신한다.

30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
puts "Play Music : #{v}"
sleep(playTime)
MusicEnding v, playTime
end
end
# .
dbus_method :stop do
end
#
dbus_signal :MusicEnding, "toto:s, time:u"
dbus_signal :MusicStart, "toto:s, time:u"
end
# .
dbus_interface "co.kr.joinc.jmusic.Manager" do
# .
dbus_method :add, "in name:s" do |name|
@music_queue.push(name)
puts "#{name} add Queue!!"
end
# .
dbus_method :getList,"out data:s" do
data = @music_queue.to_json
[data]
end
end
end
bus = DBus::SessionBus.instance

3.3. jmusic 클라이언트

그냥 서비스 메서드를 호출하는 프로그램이라서 딱히 설명할게 없다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
require 'dbus'
require 'getopt/long'
require 'json'
class Player
@session_bus = nil
@player = nil
@intro = nil
@manager_iface = nil
@player_iface = nil
def initialize
@session_bus = DBus::SessionBus.instance
service = @session_bus.service("co.kr.joinc.jmusic")
@player = service.object("/co/kr/joinc/jmusic")
introspect
# .
@manager_iface = @player["co.kr.joinc.jmusic.Manager"]
@player_iface = @player["co.kr.joinc.jmusic.Player"]
playerSignal
end
def introspect
@intro = @player.introspect
end
def showspec
return @intro
end

-i 옵션으로 jmusic의 introspect정보를 출력해 봤다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# ruby music_player.rb -i | xmllint --format -
<?xml version="1.0"?>
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="co.kr.joinc.jmusic.Player">
<method name="play">
<arg name="name" direction="in" type="s"/>
<arg name="outstr" direction="out" type="s"/>
</method>
<method name="playAll">
</method>
<method name="stop">
</method>
<signal name="MusicEnding">
<arg name="toto" type="s"/>
<arg name="time" type="u"/>
</signal>
<signal name="MusicStart">
<arg name="toto" type="s"/>
<arg name="time" type="u"/>
</signal>
</interface>
<interface name="co.kr.joinc.jmusic.Manager">
<method name="add">
<arg name="name" direction="in" type="s"/>
</method>
<method name="getList">
<arg name="data" direction="out" type="s"/>
</method>
</interface>
</node>

4. 정리

4.1. 응용

서비스를 사용하기 위해서 필요한 것들을 정리해 보자.

  1. 서비스 도메인 : 서비스의 위치를 알 수 있어야 한다.
  2. 서비스 인터페이스 : 서비스와 연결하기 위한 "인터페이스"가 있어야 한다.
  3. 서비스 메서드
    • 메서드를 위한 매개변수
    • 반환 값
  4. 서비스로 부터의 비동기 적인 메시지를 처리 하기 위한, 시그널 핸들

이들 구성요소는 원격에서 서비스를 호출하기 위한 최소한의 요구사항들이다. 다른 IPC 혹은 RPC 모두 위의 구성에서 크게 벗어나지 않는다.

OpenAPI와 MQTT를 이용한 원격/로컬 메시지 전송 프로토콜을 설계 할 때, DBus의 구성을 응용해 봐야 겠다.

4.2. DBus와 MQTT

메시징 기반의 IPC로 사용 할 수 있는 도구로 MQTT를 생각해 볼 수 있다. 두 개의 툴을 비교해 보는 것도 재미있겠다. MQTT를 IPC 용도로 사용하는 것은 따로 실험해 봐야 겠다.

4.3. Avahi

DBus를 공부한 목적은 Avahi와 통신하기 위해서다. Avahi와의 통신을 테스트해봐야 겠다.


DBUS 에 대해서

DBUS는 기본적으로 IPC(:12)의 고급구현을 위해서 만들어졌다. 고급구현인 만큼 IPC(:12)의 또다른 구현중 하나라고 볼 수 있을 것이다. 물론 다른 몇가지 IPC고급 구현이 있다.

DBUS 이전의 IPC 구현으로는 CORBA(:12)가 있다. CORBA는 객체지향 프로그래밍을 지원하는 강력한 IPC 구현이다. 이에 DCOP이라는 좀더 가볍지만 충분히 강력한 IPC 프레임워크가 등장하였다. SOAP와 XML:::RPC(:12)는 web service에 사용하기 적당하다.

DBUS는 데스크탑 응용과 OS와의 통신이 가능하도록 설계된 가볍고 충분히 강력한 IPC 구현이다.

데스크탑 응용 communication

데스크탑에는 여러개의 다수의 응용프로그램이 동시에 실행된다. 이들은 때때로 서로 통신해야할 필요가 있다. K 데스크탑 환경의 경우에는 DCOP가 사용되지만 단지 QT에만 사용될 수 있다. Bonobo 는 GNOME환경에서 사용되는데, CORBA 기반으로 너무 무겁다.또한 GObject에 강하게 의존적이라서 GNOME 밖에서는 사용하기가 쉽지 않다. DBUS는 DCOP와 Bonobo를 대체할 목적으로 만들어진 간단한 IPC로 이들 데스크탑 환경을 통합하고 있다. 또한 D-BUS는 별다른 의존성 걱정없이 사용할 수 있도록 설계되어져 있다.

데스크탑/운영체제 communication

보수적으로 보자면 운영체제(:12)시스템은 단지 kernel(:12)만을 의미하지만, 일반적으로 시스템 daemon(:12) 프로그램까지를 포함한 것으로 확장해서 보고 있다.

DBUS는 udev, device가 추가되었을 때의 signal을 잡아내는 (USB 카메라와 같은)등의 기능을 제공한다. DBUS는 데스크탑의 하드웨어와 통합되어 있으며, 유저로 하여금 이것들을 효과적으로 다룰 수 있도록 도와준다.

QT 에서의 DBUS

데스크탑 환경에서는 그나마 QT에 좀 익숙한 관계로 QT에서의 DBUS의 사용법을 위주로 살펴보려고 합니다.

참고문서


D-Bus란?
D-Bus는 프로세스간 통신 (IPC)을 위한 시스템이다. 오픈 소스 소프트웨어로써 리눅스 데스크톱의 프로세스 통신을 위한 수단으로 널리 사용되고 있다. 특히 KDE와 Gnome같은 데스크탑 환경에서 중요하게 사용한다. 구조적으로는 몇 가지 계층 이 있다

• libdbus: Libdbus는 응용 프로그램간의 통신을 가능하게 하는 라이브러리이다.
• message bus daemon: DBUS데몬은 실행 파일이며 libdbus를 기반으로 하여 제작되었으며 여러 개의 응용프로그램에서 연결이 가능하다. 데몬은 버스에 연결된 하나 이상의 프로세스에 라우팅 및 루트 메시지를 처리한다.
• wrapper library 또는 binding: libdbus-glib, libdbus-qt 등 특정 어플리케이션 프레임워크 혹은 파이썬과 같은 언어에 wrapping/binding 가능하다. D-Bus 프로그래밍을 쉽게 할 수 있도록 도와준다. 즉 libdbus는 high-level binding을 위해 low-level backend로 다루어진다고 볼 수 있다. 많은 libdbus api가 binding을 위해 만들어졌다.

 dbus-schema-1.png


그림 1. D-bus 가시적인 구조


libdbus는 오직 1:1 연결만 지원하지만 라우터 역할을 하는 message bus daemon과 message를 이용한다면 제약을 극복할 수 있다.

여기서 D-bus데몬에 대해서 좀더 살펴보자.
D-버스는 각 프로세스는 양방향 연결을 통해 연결되어있는 라우터의 일종으로 간주 할 수 있다.
D-버스는 백그라운드에서 실행되는 서비스 데몬으로써 우리는 응용 프로그램간의 기능 및 통신을 하는데 버스 데몬을 사용한다. 버스 데몬은 응용 프로그램에서 메시지 전달 및 수신의 역할을 수행한다.

D-버스 데몬은 두 가지 유형이 있는데 Session Bus 및 System Bus가 바로 그것이다.
System Bus는 커널과 다양한 시스템 전체의 이벤트와 통신 할 수 있다. 하드웨어 추상화 계층 (HAL), 네트워크 매니저이나 udev등은 System Bus를 사용하는 대표적인 응용프로그램이다. 또한 System Bus는 임의의 응용프로그램들이 시스템 이벤트를 spoof(도용)하지 못하도록 되어있다.

 figure2-systembus.png
그림 2. System Bus


반면 각 사용자 세션에 부착되는 데몬을 Session Bus 불린다.
사용자에 의해 실행된 응용프로그램은 같은 시스템내의 다른 응용프로그램 사이의 통신을 위해 사용한다. 물론 시스템 버스로부터 메세지를 받을 수 있으며, 반대로 보내는 것에는 제약이 있다.

 figure1-sessionbus.png
그림 3. Session Bus

 


주요 용어 및 컨셉

 

BUS Names 
1개의 연결에 대한 유일한 이름. 중복되어서는 안 된다.
D-BUS daemon에 연결되면 자동으로 unique한 bus name을 할당
한 번 사용된 bus name은 다시 사용되지 않는다.
사용자가 직접 이름을 등록할 수도 있다.(Optional) (ex : “org.freedesktop.TextEditor”)


Native Objects and Object Paths
D-Bus는 주고 받는 모든 메시지가 소스와 목적지가 있는 peer-to-peer 프로토콜이다. 메시지를 주고 받는데 쓰이는 주소들은 object path로 나타낸다. Object path는 표준 유닉스 파일 시스템 경로 형식을 사용한다. 유닉스 파일 시스템 경로와 다른 점이라면 숫자, 문자, 밑줄, / 만 사용할 수 있다는 점이다.

 dbus-schema-3.png
그림 4. D-bus에서 두 응용프로그램간 대화


Objects의 집합을 가진 D-Bus를 사용하는 모든 응용프로그램들에서 메시지들은 어플리케이션이 아닌 특정한 objects로 전달되거나 그것으로부터 전송 받으며 object path로 구분한다.  모든 object들은 하나 혹은 그 이상의 인터페이스를 제공하는데, 인터페이스는 메쏘드 이름의 namespace로 사용되며 단일 object는 여러 메쏘드를 가지고, 메쏘드는 여러 인터페이스를 가진다.

Interface 
method와 signal을 group화 한 이름
하나의 object는 여러 interface를 가질 수 있다.
interface 이름 (ex: “org.freedesktop.Introspectable”)

 

예)dbus-c++ library에서 사용하는 Interface를 XML로 작성, dbusxx-xml2cpp 툴을 통해 c++ 헤더파일로 generate한다.

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" ?> 
<node name="/org/freedesktop/DBus/Examples/Echo"
    <interface name="org.freedesktop.DBus.EchoDemo"
        <method name="HelloString"
            <arg type="s" name="name" direction="in"/> 
            <arg type="s" name="greeting" direction="out"/> 
        </method
        <signal name="EchoCount"
            <arg type="y" name="count"/> 
        </signal
    </interface
</node>


Method 
다른 object에 의해 호출
선택적으로 input (arguments or “in parameters”)과 output (return values or “out parameters”)을 가진다.

Signal 
broadcast 메시지로 하나의 object에서 해당 signal에 대해 관심이 있는 여러 object로 전달된다. (event)

Message 
프로세스간 전달되는 데이터
Method Call Message
Method Return Message
Error Message
Signal Message
Header와 Body로 구성

관련 사이트 : http://dbus.freedesktop.org/doc/dbus-tutorial.html

여러가지 DBus 바인딩 : https://www.freedesktop.org/wiki/Software/DBusBindings/



반응형
Posted by Real_G