🐙

rclcpp code reading 1 (Nodeの起動とSingleThreadedExecutor)

2023/11/19に公開

はじめに

rclcppのコードリーディングをしています。
基本的にはardent branchのものを読んでいます。(コード量が少ないのと、把握してから変更点を追いかける方針)
なんちゃってロボットエンジニアなので間違っていれば教えてください。
コードは下記から引用しております。
https://github.com/ros2/rclcpp
https://github.com/ros2/rcl
https://github.com/ros2/rcutils
https://github.com/ros2/tutorials

Nodeの起動について

ros tutorialのlistener.cppを見てみる。

https://github.com/ros2/tutorials/blob/34a1ab4c66fdf1d62c3b86639fce00969502bd0d/rclcpp_tutorials/src/topics/listener.cpp#L22-L38

  1. init
  2. nodeをつくって
  3. spin(node)
    という流れ。

initについて

utilities.hpp/cppにて実装

https://github.com/ros2/rclcpp/blob/bea1a52e24eaea0ad141a4d13dfa4606fcf190e7/rclcpp/include/rclcpp/utilities.hpp#L53-L55

詳しくは、そのうち書きたい。。。
とりあえず
init => rcl_initでrclを初期化して、シグナルハンドラ類を設定

spinについて

executors.hpp/cppに実装があります。SingleThreadExecutorにnodeを渡して、executorのspinに処理を渡します。executorはNodeBaseInterface::SharedPtrを保持しますが、Node::SharedPtrを受け取れるよう、ラッパが書いてありますね。

https://github.com/ros2/rclcpp/blob/bea1a52e24eaea0ad141a4d13dfa4606fcf190e7/rclcpp/include/rclcpp/executors.hpp#L42-L48

https://github.com/ros2/rclcpp/blob/bea1a52e24eaea0ad141a4d13dfa4606fcf190e7/rclcpp/src/rclcpp/executors.cpp#L30-L43

Executor

SingleThreadedExecutor

Executorを継承していて、rclcpp::okかつspinningがtrueの間、
実行可能なコールバックを取ってきて、実行するというもの。

https://github.com/ros2/rclcpp/blob/bea1a52e24eaea0ad141a4d13dfa4606fcf190e7/rclcpp/include/rclcpp/executors/single_threaded_executor.hpp#L40

https://github.com/ros2/rclcpp/blob/bea1a52e24eaea0ad141a4d13dfa4606fcf190e7/rclcpp/src/rclcpp/executors/single_threaded_executor.cpp#L25-L35

注1
spinningはstd::atomic<bool>なので,exchange/store/loadとか書いているが
一旦はただのboolだと思ってもらってもいいです。(怒られそう)
if(spinning.exchange(true)) は
prev_spinning = spinning
spinning = true
if(prev_spinning)
要するに、spinning.exchenge(true)はspinningをtrueに変更する。返り値は変更する前の値。
spinning.load は spinning
spinning.store(false) は spinning = false

注2
面白いのが、RCLCPP_SCOPE_EXITですね。スコープを抜けたとき(上の場合spin関数を抜けるとき)にthis->spinning.store(false) が実行されます。

get_next_executable()

細かいところは省略しますが、
get_next_executable => get_next_ready_executableが呼ばれます。
要するに何かしら処理するものがあったらptr、なければnullptrを返します。

https://github.com/ros2/rclcpp/blob/bea1a52e24eaea0ad141a4d13dfa4606fcf190e7/rclcpp/src/rclcpp/executor.cpp#L551-L556

https://github.com/ros2/rclcpp/blob/bea1a52e24eaea0ad141a4d13dfa4606fcf190e7/rclcpp/src/rclcpp/executor.cpp#L523-L549

AnyExecutable型は↓のようになっており、
複数持ってるptrどれか一つのみが有効になるようなクラスのようです。

https://github.com/ros2/rclcpp/blob/bea1a52e24eaea0ad141a4d13dfa4606fcf190e7/rclcpp/include/rclcpp/any_executable.hpp#L34-L53

そして、execute_any_executableで実際に実行されますが、
AnyExecutable型の中で有効なptrに対し、関数を呼び分けているだけですね。
https://github.com/ros2/rclcpp/blob/bea1a52e24eaea0ad141a4d13dfa4606fcf190e7/rclcpp/src/rclcpp/executor.cpp#L240-L260

timerの実行

TimerBaseのコールバックを呼んでます。
https://github.com/ros2/rclcpp/blob/bea1a52e24eaea0ad141a4d13dfa4606fcf190e7/rclcpp/src/rclcpp/executor.cpp#L314-L318

subscriptionの実行

rcl側からメッセージを取得して。SubscriptionBaseのhandle_messageを呼んでます。
https://github.com/ros2/rclcpp/blob/bea1a52e24eaea0ad141a4d13dfa4606fcf190e7/rclcpp/src/rclcpp/executor.cpp#L271-L289

そして、any_call_back_.dispatchしており、ここではこれ以上深堀しませんが、
create_subscription時に登録した、cbが呼ばれます。
https://github.com/ros2/rclcpp/blob/bea1a52e24eaea0ad141a4d13dfa4606fcf190e7/rclcpp/include/rclcpp/subscription.hpp#L184-L195

serviceの実行

rcl側からrequestを取り出して、サービスを実行します。
https://github.com/ros2/rclcpp/blob/bea1a52e24eaea0ad141a4d13dfa4606fcf190e7/rclcpp/src/rclcpp/executor.cpp#L320-L338

rcl側からresponseを取り出して、コールバックを起動
https://github.com/ros2/rclcpp/blob/bea1a52e24eaea0ad141a4d13dfa4606fcf190e7/rclcpp/src/rclcpp/executor.cpp#L341-L358

ここは、少し複雑なので、別に調べます。

ざっくりと

executorにnodeを渡せば、実行できそうなもの取ってきて、いい感じに実行してくれる。

コメント

排他実行の仕組みや、どうやってrcl側から、readyが伝わるのかなど、
もっと深堀が必要な部分がたくさんありますが。
なんとなくnodeの中身の動きが分かってきたような気がします。
次は、インターフェースクラス周りを読み進めてみようと思います。

Discussion