tshell_blog

ソフトウェアと車輪がついた乗り物のはなし

Application InspectorをDockerで動かす

米Microsoft、ソースコード解析ツール「Application Inspector」を公開とのニュースを見つけたのでApplication InspectorをDockerで動かしてみました。
Alpineをベースにしましたがmcr.microsoft.com/dotnet/core/sdkをベースにしてもイメージのサイズは同じだったのでどちらでもよいと思います。
自分で.NET Core SDKを入れたほうが勉強にはなるかも・・・

環境

以下の環境を使用しました。

  • ubuntu 18.04 LTS
    • kernel 5.3.0-40-generic
  • Docker version 18.09.7, build 2d0083d

Dockerfileの作成

を見ながら 以下のようなDockerfileを作成します。
2つのバージョン間でdiffをとったりできるようなので,ソースをマウントするディレクトリはsrc1とsrc2の2つを作りました。結果は/outputに出力させるようにします。

# docker build --rm -t appinspector:latest .
FROM alpine

# .NET Coreのインストール
RUN apk add --no-cache libstdc++ libintl && \
    mkdir /dotnet && \
    wget https://download.visualstudio.microsoft.com/download/pr/f686a328-a7e2-403c-9107-2e8d8b54aaa9/a1ed4732a9388c5817f79564e3fbbc3a/dotnet-sdk-3.1.102-linux-musl-x64.tar.gz && \
    tar zxf dotnet-sdk-3.1.102-linux-musl-x64.tar.gz -C /dotnet && \
    rm -f dotnet-sdk-3.1.102-linux-musl-x64.tar.gz

ENV DOTNET_ROOT=/dotnet
ENV PATH=$PATH:/dotnet

# Application Inspectorのインストール
WORKDIR /appinspector
RUN apk add --no-cache git icu-libs && \
    git clone https://github.com/microsoft/ApplicationInspector.git && \
    cd ApplicationInspector && \
    dotnet publish && \
    cp -r /appinspector/ApplicationInspector/AppInspector/bin/Debug/netcoreapp3.0/publish /appinspector/publish && \
    rm -fr /appinspector/ApplicationInspector && \
    mkdir /src1 /src2 /output

Dockerfileを作成したらイメージをビルドします。

$ docker  build --rm -t appinspector:latest

使用方法

ApplicationInspectorでApplicationInspectorを解析してみます。

$ git clone https://github.com/microsoft/ApplicationInspector.git
$ cd ApplicationInspector
$ docker run --rm -it -v $(pwd):/src1 -v /home/username:/output appinspector:latest

コンテナが起動したら以下を実行します。

# dotnet publish/ApplicationInspector.dll analyze -s /src1
# cp -r publish/html /output
# cp publish/output.html /output

結果の表示

出力結果のoutput.htmlをブラウザで開くと以下のような画面が表示されます。

f:id:tshell:20200303211155p:plain

下部の青いViewReportボタンをクリックすると以下のような画面が表示され,ファイル構成などがわかります。

f:id:tshell:20200303211214p:plain

上部のFeaturesをクリックすると以下のように検出された項目がカテゴリごとに表示されます。

f:id:tshell:20200303211232p:plain

がめんがきれいでいいとおもいます(小並感

Neo4JをNode.jsから使う

tshell.hatenablog.com

Neo4JをDockerでお手軽に試してみました。
実用的なアプリケーションを作るとなるとPythonやらJavascript(Node.js)などから使えるようにしたいです。
公式ドキュメントのDriver Manualを見ながら,Node.jsでNeo4Jにアクセスしてみます。

環境

以下の環境で試しました。

  • Windows 10 Professional version 1903
  • Node.js 12.10.0

インストール

Driver ManualのGet Started .NETJavaJavascript用ドライバ(Neo4Jアクセス用ライブラリ)のインストール方法が記載されています。
今回はJavascriptを使うので,1.2. Driver versions and installationの中のExample 1.1. Acquire the driverのタブをJavascriptにすると書いてある内容を実行していきます。

以下のコマンドを実行してインストール可能なneo4j-driverの一覧を表示します。

> npm show neo4j-driver@* version

4.0.0をインストールすることにします。

> npm install neo4j-driver@4.0.0

サンプルコードの作成

Get Startedに載っているサンプルは残念ながら動きませんでした。
以下のようなソースをindex.jsとして作成します。

neo4j = require('neo4j-driver');

const uri = 'bolt://localhost';
const user = 'neo4j';
const password = 'neo4j';

const driver = neo4j.driver(uri, neo4j.auth.basic(user, password));
const session = driver.session();

session
.run(
    'CREATE (a:Greeting) SET a.message = $message RETURN a.message + ", from node " + id(a)',
       { message: 'hello, world' }
)
.then(result => {
    session.close();
    const singleRecord = result.records[0];
    const greeting = singleRecord.get(0);

    console.log(greeting);
})
.catch(error =>{
    session.close();
    throw error;
});

以下のコマンドで実行します。

node index.js

Neo4Jへの書き込みに成功すると以下のようなレスポンスが表示されます。

hello, world, from node 343

結果の確認

http://localhost:7474にアクセスして以下コマンドを実行します。 Neo4JのWebインターフェース,コマンド入力部で改行するにはShift + Enterを入力します。

MATCH (a:Greeting)
RETURN a

以下のようにノードが表示されます。ここではindex.jsを2回実行してしまったので2つ表示されています。

f:id:tshell:20200218214559p:plain

WHERE句を使えば指定したidのノードだけ表示させることができます。

MATCH (a:Greeting)
WHERE id(a) = 343
RETURN a

f:id:tshell:20200218214734p:plain

Docker for WindowsでNeo4Jを動かす

グラフデータベースNeo4JをDockerで動かしてみました。 SQLなどの一般的にイメージされるデータベースと異なり,グラフ構造のデータを格納することができるデータベースです。ノードとノードのつながりを表現することができるので,要求と仕様を対応させられるツールが作れそうだな~なんて夢が広がります。

環境

以下の環境を使いました。

  • Windows 10 Professional version 1903
  • Docker CE 19.03
Client: Docker Engine - Community
 Version:           19.03.5
 API version:       1.40
 Go version:        go1.12.12
 Git commit:        633a0ea
 Built:             Wed Nov 13 07:22:37 2019
 OS/Arch:           windows/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          19.03.5
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.12.12
  Git commit:       633a0ea
  Built:            Wed Nov 13 07:29:19 2019
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          v1.2.10
  GitCommit:        b34a5c8af56e510852c35414db4c1f4fa6172339
 runc:
  Version:          1.0.0-rc8+dev
  GitCommit:        3e425f80a8c931f88e6d94a8c831b9d5aa481657
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683
  • Neo4j:4.0

インストール

コマンドプロンプトまたはPowershellで以下のコマンドを実行してNeo4JのDockerイメージを取得しておきます。

> docker image pull neo4j:4.0

コンテナの起動

以下のコマンドでコンテナを起動します。コマンドを実行するディレクトリがコンテナの/dataにマウントされます。

コマンドプロンプトの場合

> docker run -p 7474:7474 -p 7687:7687 -v "$pwd:/data" neo4j:4.0

Powershellの場合

> docker run -p 7474:7474 -p 7687:7687 -v "$(Get-Location):/data" neo4j:4.0

コンテナが起動すると以下のようなメッセージが表示されます。

Directories in use:
  home:         /var/lib/neo4j
  config:       /var/lib/neo4j/conf
  logs:         /logs
  plugins:      /var/lib/neo4j/plugins
  import:       /var/lib/neo4j/import
  data:         /var/lib/neo4j/data
  certificates: /var/lib/neo4j/certificates
  run:          /var/lib/neo4j/run
Starting Neo4j.
2020-02-13 04:39:51.067+0000 INFO  ======== Neo4j 4.0.0 ========
2020-02-13 04:39:51.077+0000 INFO  Starting...

Webインターフェースへのアクセス

http://localhost:7474へアクセスすると以下のような画面が表示されます。

f:id:tshell:20200217211712p:plain

以下を入力してConnectをクリックします。

  • Connect URL: bold://localhost:7687
  • Authentication type: Username / Password
  • Username: neo4j
  • Password: neo4j

f:id:tshell:20200217211748p:plain

ログインに成功すると以下のようなページが表示されます。

f:id:tshell:20200217211820p:plain

サンプルデータベースの作成

サンプルの映画データベースを作成してみます。
Webインターフェース上部のコマンド入力部分に:play movie graphを入力し,右側の▷をクリックします。

f:id:tshell:20200217211915p:plain

下部にある>をクリックして2ページ目に移動するとcypherスクリプトが表示されます。枠の中をクリックします。

f:id:tshell:20200217211954p:plain

上部のコマンド入力部分に空くrぷ都がコピーされるので,右側の▷をクリックします。

f:id:tshell:20200217212042p:plain

以下のようにグラフが表示されます。

f:id:tshell:20200217212112p:plain

データの取得

キアヌリーヴスの出演映画を取得してみましょう。
以下のコマンドをコマンド入力部に入れ,右側の▷をクリックします。
コマンド入力部ではShift + Enterで改行できます。

MATCH (actor:Person)-[r:ACTED_IN]->(movie)
WHERE actor.name = "Keanu Reeves"
RETURN actor,movie

実行すると以下のようなグラフが表示されます。
(ジョンウィックがない・・)

f:id:tshell:20200217212258p:plain

表示形式はグラフのほかにもレスポンスのテキスト形式などに切り替えることができます。

f:id:tshell:20200217212350p:plain

まとめ

Neo4JをDocker for Windowsで起動し,チュートリアルを実行できました。
グラフデータベース,何かにつかえそうですね,なにかに。

ROS2 Pub/Subサンプル

ROSやROS2ではPub/Sub方式で他のノードと通信することができます。
PublisherノードとSubscriberノードを作成して,Pub/Sub通信をしてみます。

プロジェクトの作成

ROS2開発環境のDockerコンテナを起動します。
Dockerイメージの作成方法,使用方法は以下の記事にまとめています。

tshell.hatenablog.com

tshell.hatenablog.com

コンテナを起動したら以下のコマンドを実行し,pubsubプロジェクトを作成していきます。

$ source /opt/ros/dashing/setup.bash
$ cd /home
$ mkdir src
$ cd src
$ ros2 pkg create --build-type ament_cmake pubsub --dependencies rclcpp

作成されたpubsubディレクトリ内のsrc ディレクトリに移動し,publisher.cppとsubscriber.cppを作成していきます。

ソースの記述

publisher.cppの作成

以下の内容でpublisher.cppを作成します。

#include <memory>
#include <chrono>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

using namespace std::chrono_literals;

class Publisher : public rclcpp::Node
{
    public:
    Publisher() : Node("publisher_node")
    {
        timer_ = create_wall_timer(1s, std::bind(&Publisher::timer_callback, this));
        publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);

    }

    private:
    void timer_callback()
    {
        auto message = std_msgs::msg::String();
        message.data = "Hello World";
        RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
        publisher_->publish(message);
    }
    rclcpp::TimerBase::SharedPtr timer_;
    rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
};

int main(int argc, char *argv[])
{
    rclcpp::init(argc, argv);
    rclcpp::spin(std::make_shared<Publisher>());
    rclcpp::shutdown();
    return 0;
}

subscriber.cppの作成

以下の内容でsubscriber.cppを作成します。

#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

using std::placeholders::_1;

class Subscriber : public rclcpp::Node
{
    public:
    Subscriber() : Node("subscriber_node")
    {
        subscriber_ = this->create_subscription<std_msgs::msg::String>("topic", 10, std::bind(&Subscriber::topic_callback, this, _1));
    }

    private:
    void topic_callback(const std_msgs::msg::String::SharedPtr msg) const
    {
        RCLCPP_INFO(this->get_logger(), "Subscribing: '%s'", msg->data.c_str());
    }
    rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscriber_;
};


int main(int argc, char *argv[])
{
    rclcpp::init(argc, argv);
    rclcpp::spin(std::make_shared<Subscriber>());
    rclcpp::shutdown();
    return 0;
}

CMakeLists.txtの編集

ros2 pkg createコマンドで自動生成されたCMakeLists.txtを編集し,以下のような内容にします。

cmake_minimum_required(VERSION 3.5)
project(pubsub)

# Default to C99
if(NOT CMAKE_C_STANDARD)
  set(CMAKE_C_STANDARD 99)
endif()

# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
  set(CMAKE_CXX_STANDARD 14)
endif()

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)

add_executable(publisher src/publisher.cpp)
ament_target_dependencies(publisher rclcpp std_msgs)
install(TARGETS
publisher
DESTINATION lib/${PROJECT_NAME}
)

add_executable(subscriber src/subscriber.cpp)
ament_target_dependencies(subscriber rclcpp std_msgs)
install(TARGETS
subscriber
DESTINATION lib/${PROJECT_NAME}
)

if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  # the following line skips the linter which checks for copyrights
  # uncomment the line when a copyright and license is not present in all source files
  #set(ament_cmake_copyright_FOUND TRUE)
  # the following line skips cpplint (only works in a git repo)
  # uncomment the line when this package is not in a git repo
  #set(ament_cmake_cpplint_FOUND TRUE)
  ament_lint_auto_find_test_dependencies()
endif()

ament_package()

ビルド

上記の3ファイルを編集したら以下のコマンドを実行してビルドします。

$ cd /home
$ colcon build

動作確認

Publisherの起動

ROS2開発環境のDockerコンテナ内で以下のコマンドを実行し,Publisherを起動します。

$ cd /home/src
$ source install/setup.bash
$ ros2 run pubsub publisher

以下のように1秒毎に文字列がPublishされます。
f:id:tshell:20200216193717p:plain

Subscriberの起動

ROS2開発環境のDockerコンテナをもう一つ起動し,以下のコマンドを実行してSubscriberを起動します。

$ source /opt/ros/dashing/setup.bash
$ cd /home/src
$ source install/setup.bash
$ ros2 run pubsub publisher

以下のようにSubscribeした文字列を表示します。
publisher.cppとsubscriber.cppで同じトピックを指定しているので上記で起動したPublisherが出力した文字がSubscriberに表示されます。
f:id:tshell:20200216193920p:plain

Docker環境でROS2プログラム開発

前回作成したROS2開発環境のDockerイメージを使ってROS2プログラムを作っていきます。

tshell.hatenablog.com

以下のような流れです。

  1. パッケージの作成
  2. ソースの記述
  3. CMakeLists.txtの編集
  4. package.xmlの編集
  5. ビルド
  6. 動作確認

コンテナの起動

以下のようにして/etc/group/etc/passwdをマウントし,Dockerコンテナのシェルを一般ユーザーで起動するようにするとソースの記述やCMakeLists.txtを編集するときにsudoを使う手間がなくなります。

以降の手順ではros2 pkg createによってディレクトリやファイルが自動生成されるので,空のディレクトリ内で以下を実行します。

$ docker run --rm -it -v `pwd`:/home -v /etc/group:/etc/group:ro -v /etc/passwd:/etc/passwd:ro -u $(id -u $USER):$(id -g $USER) ros2:dashing 

コンテナ起動後に以下を実行します。

$ source /opt/ros2/dashing/setup.bash

パッケージの作成

パッケージはsrcディレクトリ内に作成する必要があります。まずはsrcディレクトリを作成し,その中でros2 pkg createコマンドを実行します。

ビルド時にはここで作成したsrcディレクトリの一つ上(ここでは/home)でcolcon buildコマンドを実行する必要があります。

$ cd /home
$ mkdir src
$ cd src
$ ros2 pkg create --build-type ament_cmake sample --dependencies rclcpp

sample/src内にsample.cppを作成

上のようなros2 pkg createコマンドを実行するとsampleディレクトリが作成され,その中にsrcディレクトリが作成されます。
以下のようなsample.cppをsrcディレクトリ内に作成します。

#include <memory>
#include <chrono>
#include "rclcpp/rclcpp.hpp"

using namespace std::chrono_literals;

class SampleClass : public rclcpp::Node
{
    public:
    SampleClass() : Node("sample_node")
    {
        timer_ = create_wall_timer(1s, std::bind(&SampleClass::timer_callback, this));
    }
    private:
    void timer_callback()
    {
        RCLCPP_INFO(this->get_logger(), "Hello World");
    }
    rclcpp::TimerBase::SharedPtr timer_;
};

int main(int argc, char *argv[])
{
    rclcpp::init(argc, argv);
    rclcpp::spin(std::make_shared<SampleClass>());
    rclcpp::shutdown();
    return 0;
}

CMakeLists.txtの編集

CMakeLists.txtを以下のように編集します。
Dockerコンテナを立ち上げたディレクトリ内にあるファイルを適当なエディタで編集すればDockerコンテナ内にも反映されます(当たり前ですが)

cmake_minimum_required(VERSION 3.5)
project(sample)

# Default to C99
if(NOT CMAKE_C_STANDARD)
  set(CMAKE_C_STANDARD 99)
endif()

# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
  set(CMAKE_CXX_STANDARD 14)
endif()

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)

add_executable(sample_package src/sample.cpp)
ament_target_dependencies(sample_package rclcpp)
install(TARGETS
sample_package
DESTINATION lib/${PROJECT_NAME}
)

if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  # the following line skips the linter which checks for copyrights
  # uncomment the line when a copyright and license is not present in all source files
  #set(ament_cmake_copyright_FOUND TRUE)
  # the following line skips cpplint (only works in a git repo)
  # uncomment the line when this package is not in a git repo
  #set(ament_cmake_cpplint_FOUND TRUE)
  ament_lint_auto_find_test_dependencies()
endif()

ament_package()

package.xmlの編集

package.xmlを以下のように編集します。

<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
  <name>sample</name>
  <version>0.0.0</version>
  <description>TODO: Package description</description>
  <maintainer email="root@todo.todo">root</maintainer>
  <license>TODO: License declaration</license>

  <buildtool_depend>ament_cmake</buildtool_depend>

  <test_depend>ament_lint_auto</test_depend>
  <test_depend>ament_lint_common</test_depend>

  <build_depend>rclcpp</build_depend>
  <exec_depend>rclcpp</exec_depend>

  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>

ビルド

ros2 pkg createコマンドを実行したディレクトリの一つ上(ここでは/home)に移動し,colcon buildを実行します。

$ cd /home
$ colcon build

動作確認

ビルドに成功したら以下のコマンドを実行し,動作確認を行います。

$ source install/setup.bash
$ ros2 run sample sample_package

ここでsampleros2 pkg createコマンド実行時に与えたパッケージ名,sample_packageはCMakeLists.txtで指定したexecutable名です。
これらを変えれば好きな名前にできます。

上記のコマンドを実行すると以下のように1秒毎に文字列が表示されます。

[INFO] [sample_node]: Hello World
[INFO] [sample_node]: Hello World
[INFO] [sample_node]: Hello World
[INFO] [sample_node]: Hello World

開発環境コンテナでROS2プログラムを作成することができました。
VSCode拡張機能か何かを使ってラクしたいですね〜

ROS2開発環境をDockerで構築する

何番煎じであろうと私はまだ煎じていない

ROS2の勉強始めよっかな〜と思い立ったので ROS2ではじめよう 次世代ロボットプログラミングの第2章を参考にROS2環境をDockerで構築してみました。

セットアップ方法を見ていると「ROS1環境があるところにROS2をセットアップすると環境変数とか依存関係で大変なことになる場合があります」というようなことが書いてあって,そのままインストールするとあとで大変になりそうなのでDockerイメージとして構築してみます。

Dockerfileの作成とイメージのビルド

以下のようなDockerfileを作成します。

# docker build -t ros2:dashing .
FROM ubuntu:18.04
ENV DEBIAN_FRONTEND noninteractive

ENV ROS_DISTRO dashing

# ロケールのセットアップ
RUN apt-get update && apt-get install -y locales && \
    dpkg-reconfigure locales && \
    locale-gen ja_JP ja_JP.UTF-8 && \
    update-locale LC_ALL=ja_JP.UTF-8 LANG=ja_JP.UTF-8
ENV LC_ALL   ja_JP.UTF-8
ENV LANG     ja_JP.UTF-8
ENV LANGUAGE ja_JP.UTF-8

# APTソースリストの設定
RUN apt-get update && \
    apt-get install -y curl gnupg2 lsb-release && \
    curl http://repo.ros2.org/repos.key | apt-key add - && \
    sh -c 'echo "deb [arch=amd64,arm64] http://packages.ros.org/ros2/ubuntu \
    `lsb_release -cs` main" > /etc/apt/sources.list.d/ros2-latest.list' && \
    apt-get update

# ROS2パッケージのインストール
RUN export ROS_DISTRO=dashing && \
    apt-get install -y ros-$ROS_DISTRO-desktop \
    python3-colcon-common-extensions python3-rosdep python3-argcomplete && \
    rosdep init && \
    rosdep update

## 環境設定
RUN    echo "source /opt/ros/$ROS_DISTRO/setup.bash" >> ~/.bashrc

Dockerfileを保存したディレクトリの中で以下を実行します。

$ docker build --rm -t ros2:dashing .

30分以上かかるので気長に待ちます。

動作確認

demo_nodes_cppに含まれるTalkerとListenerを使って動作確認してみます。
うまく行けばTalkerで出力された内容がListenerで受信されることを確認できます。

ターミナルを2つ開き,それぞれで以下のコマンドを実行してコンテナを起動します。

$ docker run --rm -it ros2:dashing

Listenerの起動

一方のターミナルで以下のコマンドを実行し,Listenerを起動します。

# ros2 run demo_nodes_cpp listener

Talkerの起動

もう一方のターミナルで以下のコマンドを実行し,Talkerを起動します。

# ros2 run demo_nodes_cpp talker

表示例

Talkerの表示例
f:id:tshell:20200213174147p:plain

Listenerの表示例
f:id:tshell:20200213174223p:plain

以上のようにTalkerの内容がListenerに反映されれば正常に動作しています。

RealTime処理のサンプルプログラム

tshell.hatenablog.com

前回の記事ではDockerコンテナ上でcyclictestを実行しました。
今回は HOWTO build a simple RT applicationを参考にリアルタイムアプリケーションを作成してみます。

Exampleに載っている内容をsample.cとして保存し,以下のコマンドでコンパイルします。

$ gcc -pthread sample.c

Dockerコンテナを起動して実行してみます。生成されたa.outがあるディレクトリ内で以下を実行します。

$ docker run --rm -it --cpu-rt-runtime=950000 --ulimit rtprio=99 --cap-add=sys_nice -v `pwd`:/home ubuntu

コンテナが起動したら/homeに移動し,./a.outを実行します。
エラーがあった場合にはエラー箇所が表示されますが,正常に動作した場合は何も表示されません。あまり面白くないですね...

周期実行サンプル

HOWTO build a basic cyclic applicationを参考に以下のようなファイルをsample_cyclic.cとして作成します。

/*                                                                  
 * POSIX Real Time Example
 * using a single pthread as RT thread
 */
 
#include <limits.h>
#include <pthread.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <time.h>
 
struct period_info {
        struct timespec next_period;
        long period_ns;
};

static void inc_period(struct period_info *pinfo) 
{
        pinfo->next_period.tv_nsec += pinfo->period_ns;
 
        while (pinfo->next_period.tv_nsec >= 1000000000) {
                /* timespec nsec overflow */
                pinfo->next_period.tv_sec++;
                pinfo->next_period.tv_nsec -= 1000000000;
        }
}
 
static void periodic_task_init(struct period_info *pinfo)
{
        /* for simplicity, hardcoding a 1ms period */
        pinfo->period_ns = 1000000;
 
        clock_gettime(CLOCK_MONOTONIC, &(pinfo->next_period));
}
 
static void do_rt_task()
{
        struct timespec ts;
        clock_gettime(CLOCK_REALTIME, &ts);
        printf("time: %10ld.%09ld\n", ts.tv_sec, ts.tv_nsec);
        
        /* Do RT stuff here. */
}
 
static void wait_rest_of_period(struct period_info *pinfo)
{
        inc_period(pinfo);
 
        /* for simplicity, ignoring possibilities of signal wakes */
        clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &pinfo->next_period, NULL);
}

void *thread_func(void *data)
{
        /* Do RT specific stuff here */
        
        struct period_info pinfo;
 
        periodic_task_init(&pinfo);
 
        while (1) {
                do_rt_task();
                wait_rest_of_period(&pinfo);
        }
 
        return NULL;
}
 
int main(int argc, char* argv[])
{
        struct sched_param param;
        pthread_attr_t attr;
        pthread_t thread;
        int ret;
 
        /* Lock memory */
        if(mlockall(MCL_CURRENT|MCL_FUTURE) == -1) {
                printf("mlockall failed: %m\n");
                exit(-2);
        }
 
        /* Initialize pthread attributes (default values) */
        ret = pthread_attr_init(&attr);
        if (ret) {
                printf("init pthread attributes failed\n");
                goto out;
        }
 
        /* Set a specific stack size  */
        ret = pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN);
        if (ret) {
            printf("pthread setstacksize failed\n");
            goto out;
        }
 
        /* Set scheduler policy and priority of pthread */
        ret = pthread_attr_setschedpolicy(&attr, SCHED_FIFO);
        if (ret) {
                printf("pthread setschedpolicy failed\n");
                goto out;
        }
        param.sched_priority = 80;
        ret = pthread_attr_setschedparam(&attr, &param);
        if (ret) {
                printf("pthread setschedparam failed\n");
                goto out;
        }
        /* Use scheduling parameters of attr */
        ret = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
        if (ret) {
                printf("pthread setinheritsched failed\n");
                goto out;
        }
 
        /* Create a pthread with specified attributes */
        ret = pthread_create(&thread, &attr, thread_func, NULL);
        if (ret) {
                printf("create pthread failed\n");
                goto out;
        }
 
        /* Join the thread and wait until it is done */
        ret = pthread_join(thread, NULL);
        if (ret)
                printf("join pthread failed: %m\n");
 
out:
        return ret;
}

最初のsample.cと同様に以下のようにしてコンパイルします。

$ gcc -pthread -o cyclic sample_cyclic.c

Dockerコンテナ上で./cyclicを実行すると以下のように1msごとに時刻が表示されます。

time: 1580301619.999005823
time: 1580301620.000007537
time: 1580301620.001007728
time: 1580301620.002007485
time: 1580301620.003007308
time: 1580301620.004011275
time: 1580301620.005009829
time: 1580301620.006012556
time: 1580301620.007010412
time: 1580301620.008012001
time: 1580301620.009012342

リアルタイム処理の記述

以上でとりあえずサンプルは動きました。
自分の好きなことを好きな間隔で実行させるには以下の箇所を修正します。

static void periodic_task_init(struct period_info *pinfo)
{
        /* for simplicity, hardcoding a 1ms period */
        pinfo->period_ns = 1000000;
 
        clock_gettime(CLOCK_MONOTONIC, &(pinfo->next_period));
}
 
static void do_rt_task()
{
        struct timespec ts;
        clock_gettime(CLOCK_REALTIME, &ts);
        printf("time: %10ld.%09ld\n", ts.tv_sec, ts.tv_nsec);
        
        /* Do RT stuff here. */
}

periodic_task_init内のpinfo->period_nsを変更すれば実行周期を変えられます。

do_rt_task()の中には周期ごとに実行したい処理を記述しましょう。

以上で最低限のリアルタイムは実行できるようになります。