20200803のLinuxに関する記事は11件です。

【ROS】Ubuntu(18.04)にROS(melodic)をインストールした時のメモ

1. ROSのインストールコマンド

 Ubuntu(18.04)にROS(melodic)を入れるためのコマンド. 本当はkineticを入れたかったけどUbuntuのバージョンが18.04だとkineticは入れられないらしい.

sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list'
sudo apt-key adv --keyserver 'hkp://keyserver.ubuntu.com:80' --recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654

sudo apt-get -y update
sudo apt-get -y upgrade

sudo apt-get -y install ros-melodic-desktop-full
apt-cache search ros-melodic

sudo apt-get install python-rosdep
sudo rosdep init
rosdep update

echo "source /opt/ros/kinetic/setup.bash" >> ~/.bashrc
source ~/.bashrc
source /opt/ros/kinetic/setup.bash

sudo apt-get -y install python-rosinstall

2. ROSが入ったことの確認

 ワークスペースを作ってroscoreを実行することでROSのバージョンを確認できる. これが実行できたらROSのインストール成功.

mkdir -p ~/catkin_ws/src
cd ~/catkin_ws/src
catkin_init_workspace
cd ~/catkin_ws
catkin_make
roscore
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【ROS】Ubuntu(18.04)にROS(melodic)をインストール

1. ROSのインストールコマンド

 Ubuntu(18.04)にROS(melodic)を入れるためのコマンド. 本当はkineticを入れたかったけどUbuntuのバージョンが18.04だとkineticは入れられないらしい.

sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list'
sudo apt-key adv --keyserver 'hkp://keyserver.ubuntu.com:80' --recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654

sudo apt-get -y update
sudo apt-get -y upgrade

sudo apt-get -y install ros-melodic-desktop-full
apt-cache search ros-melodic

sudo apt-get install python-rosdep
sudo rosdep init
rosdep update

echo "source /opt/ros/kinetic/setup.bash" >> ~/.bashrc
source ~/.bashrc
source /opt/ros/kinetic/setup.bash

sudo apt-get -y install python-rosinstall

2. ROSが入ったことの確認

 ワークスペースを作ってroscoreを実行することでROSのバージョンを確認できる. これが実行できたらROSのインストール成功.

mkdir -p ~/catkin_ws/src
cd ~/catkin_ws/src
catkin_init_workspace
cd ~/catkin_ws
catkin_make
roscore
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Dockerコンテナを持ち歩こう

概要

USBメモリを使い、管理者権限一切不要でWindowsPCがあればどこでもDockerコンテナを動かせる環境を構築します。

USBメモリについて

USB3.0対応の速度の速いものを選ばないと書き込み速度に引っ張られて動作が遅くなります。

CrystalDiskMarkのランダム書き込み(右下2つ)の値が1.0を超えていないと辛いと思います(※体感には個人差があります)

ちなみに自分はI-O DATAのU3-MAX2/32Kを使っています。

多少嵩張ってもいいのならポータブルSSDにすれば不良品でもない限り速度面で問題は出ないはず。

32GBまでのUSBメモリの場合、初期状態では概ねFAT32でフォーマットされていますが、FAT32は1ファイル最大4Gまでという制限があり、1ファイルで構成されている仮想ストレージがすぐに足りなくなり書き込みエラーが発生するのでNTFSやexFATでフォーマットしなおす必要があります。

また性質上書き込みを大量に行うので最悪いつ壊れてもいいように対応はしておきましょう。

仮想化ソフトのインストール

Qemu

Dockerは(64bitの)Linux上でしか動きませんので、仮想化ソフトでLinuxを動作させる必要があります、仮想化ソフトにはQemuを使います。

Qemuはエミュレート速度はいまいちですが、管理者権限が必須な各種アクセラレーターを有効にしない状態で起動することができます。つまり、遅くてもいいなら管理者権限なしで起動することが可能です。

WSL2や他の仮想化ソフトは管理者権限が必須な上ポータブル運用はできませんし、ポータブル運用を想定したPortable-VirtualBoxは管理者権限が必須です。

アクセラレーターなしではGnome等のデスクトップ環境を動かすのは困難ですが、CUI(コマンドライン)環境ならそれなりに動きます。(※体感には個人差があります)
Docker界隈では基本的にUIはWebブラウザが担うのがほとんどなのでさほど問題ではないです。

32bitPC・OS上でも64bitPCをエミュレートできる点もポイント高いです。

ダウンロード・インストール

公式サイトからQemuのWindows版(32bit版または64bit版)をダウンロードします。

Windows版のQemuは管理者権限が必要なインストーラー形式でしか配布をしていないようなので、インストーラーの解凍に対応したUniversal Extractor2(直リンク)でUSBメモリに解凍します。

ちなみに管理者権限を使ってインストールをし、インストールされたファイルをUSBメモリにコピーしても使えます。

Linuxのインストール

Alpine Linux

QemuをUSBメモリにインストール(コピー)したら、仮想PCにLinuxをインストールします。

インストールするLinuxデストリビューションは64bit対応でDockerをインストールできるものなら何でもいいのですが、

個人的にはAlpine Linuxがオススメです。インストールが爆速です。Debian等のよく使われるLinuxデストリビューションをイチからインストールしようとすると、ただでさえ遅いQemu上では最小構成でも数時間は覚悟しないといけませんが、数分でインストールが終わります。

Alpine Linuxは誤解を恐れずざっくりいうと互換性を犠牲にして軽量化にこだわったデストリビューションで、サイズが小さいためDockerの公式イメージのベースとしてよく採用されており、Docker界隈ではよく目にします。

一部他のLinuxデストリビューションで使わているソフトウェアが使えないなど互換性に難がありますが、Alpine Linux本体はDockerホストに徹し、ソフトウェアは基本的にDockerコンテナ上で動かすという運用をすれば問題になりません。

isoダウンロード

公式サイトisoのダウンロードページがあるのでx86_64版をダウンロードします。

Standard(124M)でもいいのですが、仮想PC用のさらにサイズの小さいVirtual(40M)をお勧めします。

ダウンロードするとalpine-virt-3.12.0-x86_64.isoといった長いファイル名になるので、alpine.isoとかに変えておくと後が楽です。

インストール

Qemuは他の仮想化ソフトとは異なりすべてコマンドラインで設定・起動する仕組みになっています(GUIで動かすためのフロントエンドはいろいろありますが)

仮想ストレージファイルの作成

> qemu-img create -f qcow2 linux.qcow2 20G

最後の20Gは仮想ストレージファイルの最大容量になるので環境に合わせて変更しましょう。
いきなり20G全容量を確保するのではなく、最大容量に達するまで必要に応じてサイズが大きくなっていく仕組みになっています。

isoファイルをブートディスクにして起動

Qemuのオプションはドキュメントを見てください、自分は以下のように設定しています。

> qemu-system-x86_64.exe -display sdl -smp 2 -boot d -m 2048 -net nic,model=virtio -hda linux.qcow2 -cdrom alpine.iso

問題がなければウインドウが出てLinuxのブートを開始するはずです。ブートには1分から数分かかります。

インストール

ブートが終了するとコンソールのログイン画面が出てきますが、rootと入力してエンターを押すとパスワードなしでログインできます。

localhost login: root

ログインしたら、

# setup-alpine

でインストールを開始します。コマンドライン上でいくつか質問されるので、答えていくとインストールを始めます。

具体的にどんな内容なのかはここらへんを参考にどうぞ。長いので省略します。

インストールが終わったら

# poweroff

でシャットダウンします。

Linux起動・設定

起動

> qemu-system-x86_64.exe -display sdl -smp 2 -m 2048 -net nic,model=virtio -net user,hostfwd=tcp::22-:22,hostfwd=tcp::9000-:9000 -hda linux.qcow2

自分はこのようにして起動しています(一部を除く)。

「-net user,hostfwd=tcp::22-:22,hostfwd=tcp::9000-:9000」の部分で指定したポートを通じてQemu内Linux上のサーバーに接続できるようになります。ポートフォワーディングというらしい。
(※同一PCのローカル接続の話であって外部PCからの接続はWindowsファイアウォールやルータ等の関係でそのままでは出来ませんし管理者権限が必要です)

例えばこの場合だとlocalhost:22でWindowsホストではなく、Qemu内のLinuxで22番ポートを待ち受けしているサーバーに接続できるようになります。

ここではSSH用の22番とPortainer用の9000番を割り当てています。上記のように,でつないでいけば複数のポートを開くことができますが、すでに別のサーバーで待っているポートを指定するとQemu自体エラーで起動しないので注意してください。

ちなみに、Dockerコンテナ上のサーバーの接続にも有効です。つまり、Qemuの起動時に9000番ポートへの待ち受けを有効にしていれば、Linuxホストの9000番ポートを待ち受ける設定で起動したDockerコンテナにWindowsホストから9000番ポートで接続できるということです。

SSHの設定

インストール時同様にログイン画面が出たら、IDはroot、パスワードはインストール時に設定したパスワードでログインします。

Qemuのウインドウではコマンドのコピペが出来ず何かと不便なのでSSHを使えるようにします。

Alpine Linuxは最初からSSHサーバーが入っていますが(インストール時のSSHの質問でnoneと答えなかった場合)、SSHサーバーの仕様でそのままではrootユーザーでログイン出来ないようになっているので、ここでは手っ取り早く設定ファイルを追記してrootでログインできるようにします。

# echo "PermitRootLogin yes" >> /etc/ssh/sshd_config

さらにパスワードなしで運用する場合は次も入力します

# echo "PermitEmptyPasswords yes" >> /etc/ssh/sshd_config

設定ファイルを変更したらQemuを一度シャットダウンして再起動するかまたはSSHサーバーを再起動します

# service sshd restart

接続

最近のWindows10は標準でOpehSSHクライアントが入っていますが、そのまま使うとユーザーフォルダに.sshフォルダを作ってしまいポータブルではなくなってしまうので、以下のように設定ファイルを読み込まず、鍵のチェックをせず、KnownHostsファイルを残さないオプションをつけて起動します。

> ssh -F /dev/null -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@localhost

Dockerインストール

Alpine Linuxはapkという独自のパッケージマネージャーを持っていますが、初期状態ではdockerをインストールできないので、インストールできるようにviで/etc/apk/repositoriesを編集します。

# vi /etc/apk/repositories

初期状態では2行目のcommunityリポジトリが#でコメントアウトされていますが、#を消して有効にします。

#/media/cdrom/apks
http://dl-cdn.alpinelinux.org/alpine/v3.12/main
http://dl-cdn.alpinelinux.org/alpine/v3.12/community
#http://dl-cdn.alpinelinux.org/alpine/edge/main
#http://dl-cdn.alpinelinux.org/alpine/edge/community
#http://dl-cdn.alpinelinux.org/alpine/edge/testing

保存したら、apkでdockerとdocker-composeパッケージをインストールします

# apk update
# apk add docker docker-compose

インストールが終わったらdockerを起動します。

# service docker start

起動時に自動で開始するように設定しておきます。

# rc-update add docker boot

ちゃんと動作しているかを確認する場合は(多分)そのために用意されているhello-worldイメージをダウンロードしてコンテナとして動作させてみます。

# docker run --rm hello-world

上手く行けば以下のような表示が出るはずです。

Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
0e03bdcc26d7: Pull complete
Digest: sha256:49a1c8800c94df04e9658809b006fd8a686cab8028d33cfba2cc049724254202
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

Portainerのインストール

Dockerコンテナをすべてコマンドラインで管理するのが辛い場合は、Webブラウザ上でDockerコンテナを管理できるPortainerをお勧めします。

Dockerをインストール出来ていれば以下のコマンド1行でインストール完了です。

# docker run -d -p 9000:9000 --name=portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer

インストール完了後、Webブラウザで http://localhost:9000 にアクセスすればPortainerのログイン画面が出てくるはずです。

セキュリティ

rootでの運用はこの場合はUSBメモリをPCに接続しQemuが動作している間しか機能せず、かつ通常ローカル接続しかできないのであまり問題にはなりませんが、基本的にはやってはいけない運用であることは覚えておきましょう。またUSBメモリの紛失盗難には気を付けましょう。

さいごに

これでDockerコンテナをUSBメモリで持ち歩く環境が完成しました。
自分が自由に管理できるPCがない人におすすめです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CORS問題の対応の仕方をミスってた話

フロントエンドとサーバサイドプログラムの連携をRestっぽい感じでやるようになって久しいが
ここに来て結構なポカミスをやったので備忘録として。

フロントには固定値のjsonを返すだけのダミーAPIを作って実装を進めてもらい
バックエンドはprod用としてそのダミーが動的なものになるように実装、最終的にダミーと差し替え
みたいな流れで制作をしている。

で、バックエンドはphpを使うが色々ごちゃごちゃやった後に

test.php
色々やる
header('Content-Type: application/json');
echo $json;
exit;

としてjsonを出力するのがセオリーである。
フロントはtest.phpをエンドポイントとしてjsとかで実行するわけだが
大体いつも問題になるのがCORSである。

セキュリティリスクを追々考えなければと思いつつApache側で

Header set Access-Control-Allow-Origin *

レスポンスヘッダに全てのドメインからアクセスOK!!を付けている。
で、それをすっかり忘れてphpの方にも記載したのがまずかった。

header('Content-Type: application/json');
header('Access-Control-Allow-Origin *');
echo $json;
exit;
Access to XMLHttpRequest at 'https://xxxxx.jp/api/test' from origin 
'null' has been blocked by CORS policy: 
The 'Access-Control-Allow-Origin' header contains multiple values '*, *', 
but only one is allowed.

エラーメッセージの冒頭だけ見てOriginの設定してるはずなのにな〜って思ってたが
よく見るとAccess-Control-Allow-Originヘッダが複数設定されてるよってエラーだった。

どっちか消して解消、というかまぁこの場合はphpの方で良いと思うが。
分かってるんだけど気付かないパターンのミスが一番怖い。

おわり。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

シンボリックリンクの生成で相対パスを指定すると危険だった話

08/04追記
コメントにて補足や訂正を頂いたので、適宜修正をしていきます。

はじめに

シェルコマンドを学習しているとき、シンボリックリンクに関して沼にハマってしまったので、備忘録を残します。
これがベストプラクティスかどうかは知らないが、シンボリックリンクの生成はなるべく絶対パスを使おう。

これは時と場合によるので、ベストプラクティスとは言えません。
リンク元のファイル・リンクをまとめて管理したい場合、設置場所変更に弱くなるためです。
※Webページのリンク ( ハイパーリファレンス ) を絶対URLで書くか、相対URLで書くかと同じ問題
@angel_p_57

シンボリックリンクとは

シンボリックリンクとは、オペレーティングシステム(OS)のファイルシステムの機能の一つで、特定のファイルやディレクトリを指し示す別のファイルを作成し、それを通じて本体を参照できるようにする仕組み。
IT用語辞典 e-Words

Linuxにおけるシンボリックリンクは、Windowsでいうところのショートカットのようなものだと認識しています。

目的は似ていますが、実態は大分違います。加えて、Windowsにも ( リパース・ポイントという機能を利用した ) シンボリックリンクが存在します。
ショートカットは、指定のファイルをアプリで開くための情報を持ったランチャーに過ぎませんが、シンボリックリンクは指定のファイルの代替として使えるファイルです。
@angel_p_57

シンボリックリンクの作成

シンボリックリンクの作成に用いるコマンドは以下です。
hoge.jpgは任意のファイル名、fugaは任意のディレクトリ名です。
ここではhoge.jpgというファイルのシンボリックリンクを、fugaディレクトリに生成します。

$ ln -s hoge.jpg fuga

補足:このケースでは『既にfugaディレクトリが存在していれば』意図どおりに正常に実行されます。
fugaディレクトリがなければ、同一ディレクトリにfugaという名前のシンボリックリンクが生成されてしまいます。

$ ln -s hoge.jpg fuga/ とすれば、ディレクトリが存在していれば実行するコマンドになります。
(頂いたコメントを参考にしました)

やりたいこと

~/Picture/konosuba.jpg のシンボリックリンクを、
~/Picture/foobar に生成したい。

やってみたこと

相対パスで指定した場合(failed)

カレントディレクトリが ~/Pictureだとする。

~/Picture $ ln -s konosuba.jpg foobar

foobarディレクトリに konosuba.jpgは いちおう生成されるが、
先にいっておくと、これは間違い。

カレントディレクトリ内に存在する konosuba.jpgのシンボリックリンクが、相対パスとして指定したfoobarフォルダ内に生成される・・・というわけではない。

foobarディレクトリに生成されたシンボリックリンク konosuba.jpgは、
自身から見た相対パス konosuba.jpg つまり同ディレクトリ内のkonosuba.jpgを参照しにいく、と考えよう。

つまり、自身を参照するシンボリックリンクという謎のファイルを生成してしまう。

(これは、シェルコマンドの第一引数である"konosuba.jpg"を、シンボリックリンク自身の位置を基準とした相対パスとして読み取ってしまうため)

相対パスで指定した場合(successed)

カレントディレクトリが ~/Pictureだとする。

~/Picture $ ln -s ../konosuba1.jpg foobar

foobarディレクトリに konosuba.jpgが生成される。
こいつは正解。
foobar ディレクトリに生成されたシンボリックリンクkonosuba.jpg は、
自身の位置から見て "../konosuba1.jpg"を参照するリンクとして生成される。

慣れると問題ないのかもしれないが、正直僕にはややこしく感じました。

その場合は、-r ( --relative ) というオプションを使います。相対位置はコマンドが自動的に判断してくれますから。
ln -s hoge.jpg fuga/ だと、作成されるシンボリックリンク fuga/hoge.jpg は循環参照を起こして役に立ちませんが、ln -s -r hoge.jpg fuga/ とすれば、fuga/hoge.jpg -> ../hoge.jpg という適切なリンクを作成してくれます。
@angel_p_57

絶対パスで書く

$ ln -s ~/Picture/konosuba.jpg ~/Picture/foobar

絶対パスなら参照元が意図と異なるリスクをぐっと減らせる。
ディレクトリ構造がややこしくなると、とんでもないことになりそうな気もするが、とりあえず間違えることはなくなるはずだ・・

おわりに

なんだかうまく書けた気がしないので、適宜変更を加えていこうと思う・・

参考文献

Linuxコマンドライン入門
Ubuntu Manuals

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS開発中 sudo yum install nodejs npm --enablerepo=epelが通らなかった話

環境

EC2(Amazon Linux 2)

詰まったこと

現在AWSにて環境構築中の初学者です。アプリケーション用のEC2を作成中。
各種パッケージをインストールしていたのですがnodejs npmがインストール出来ず苦戦したことを記事にします。

@naoki_mochizuki さんの
https://qiita.com/naoki_mochizuki/items/814e0979217b1a25aa3e
という記事を参考に環境構築を行っていました。

       __|  __|_  )
       _|  (     /   Amazon Linux 2 AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-2/

[shimo0108@~]$ sudo yum install \
 > git make gcc-c++ patch \
 > openssl-devel \
 > libyaml-devel libffi-devel libicu-devel \
 > libxml2 libxslt libxml2-devel libxslt-devel \
 > zlib-devel readline-devel \
 > ImageMagick ImageMagick-devel \
 > epel-release



読み込んだプラグイン:extras_suggestions, langpacks, priorities, update-motd amzn2core                                    | 3.7 kB  00:00:00     
パッケージ 1:make-3.82-24.amzn2.x86_64 はインストール済みか最新バージョンです
パッケージ libxml2-2.9.1-6.amzn2.4.1.x86_64 はインストール済みか最新バージョンです
パッケージ epel-release は利用できません。
.
.
.

完了しました!

epel-release is available in Amazon Linux Extra topic "epel"

To use, run
# sudo amazon-linux-extras install epel

Learn more at
https://aws.amazon.com/amazon-linux-2/faqs/#Amazon_Linux_Extras

WEBアプリ用のEC2にログイン。パッケージをインストールしました。
epel-release以外は正常にinstallできた事が確認できます。
ここでなぜかepel-releaseがインストール出来なかったんですよね。

ちなみにepel-release(EPEL)とは、、、

EPEL リポジトリとは、CentOS 標準のリポジトリでは提供されていないパッケージを、yum コマンドでインストールすることを可能にするリポジトリのことです。

EPEL は、エンタープライズ向けのリポジトリなので、サードパーティー製リポジトリの中では信頼性の高いものになっています。

EPEL 以外のサードパーティ製リポジトリには、Remi、RPMForge があります。特徴としては、Remi は最新バージョンのパッケージを入手可能、RPMForge は大量のパッケージを入手可能と言われているようです。

実際には、用途やパッケージの依存関係を考慮して各リポジトリを活用することになります。標準のリポジトリでは提供されないパッケージを使うことができますが、利用に関してはCentOSのサードパーティ製のリポジトリに関する記述を考えると、自己責任、ということになります。

[shimo0108@ ~]$ sudo yum install nodejs npm --enablerepo=epel
読み込んだプラグイン:extras_suggestions, langpacks, priorities, update-motd

Error getting repository data for epel, repository not found

その後上記コマンド実行するもインストールできず。。
しかしエラーログ文の中に

To use, run
# sudo amazon-linux-extras install epel

を発見。これインストールすればいいのかなと。思ったわけです。

[shimo0108@ ~]$ sudo amazon-linux-extras install epel
Installing epel-release

リポジトリーを清掃しています: amzn2-core amzn2extra-docker amzn2extra-epel
12 個の metadata ファイルを削除しました
4 個の sqlite ファイルを削除しました
0 個の metadata ファイルを削除しました
読み込んだプラグイン:extras_suggestions, langpacks, priorities, update-motd
.
.
.

依存性の解決をしています
--> トランザクションの確認を実行しています。
---> パッケージ epel-release.noarch 0:7-11 を インストール
--> 依存性解決を終了しました。

依存性を解決しました

=======================================================================================================
 Package                   アーキテクチャー    バージョン           リポジトリー                  容量
=======================================================================================================
インストール中:
 epel-release              noarch              7-11                 amzn2extra-epel               15 k

トランザクションの要約
=======================================================================================================
インストール  1 パッケージ

総ダウンロード容量: 15 k
インストール容量: 24 k
Is this ok [y/d/N]: y
Downloading packages:
epel-release-7-11.noarch.rpm                                                    |  15 kB  00:00:00     
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  インストール中          : epel-release-7-11.noarch                                               1/1 
  検証中                  : epel-release-7-11.noarch                                               1/1 

インストール:
  epel-release.noarch 0:7-11                                                                           

完了しました!
  0  ansible2                 available    \
        [ =2.4.2  =2.4.6  =2.8  =stable ]
  2  httpd_modules            available    [ =1.0  =stable ]
  3  memcached1.5             available    \
        [ =1.5.1  =1.5.16  =1.5.17 ]
  5  postgresql9.6            available    \
        [ =9.6.6  =9.6.8  =stable ]
  6  postgresql10             available    [ =10  =stable ]
  8  redis4.0                 available    \
        [ =4.0.5  =4.0.10  =stable ]
  9  R3.4                     available    [ =3.4.3  =stable ]
 10  rust1                    available    \
        [ =1.22.1  =1.26.0  =1.26.1  =1.27.2  =1.31.0  =1.38.0
          =stable ]
 11  vim                      available    [ =8.0  =stable ]
 13  ruby2.4                  available    \
        [ =2.4.2  =2.4.4  =2.4.7  =stable ]
 15  php7.2                   available    \
        [ =7.2.0  =7.2.4  =7.2.5  =7.2.8  =7.2.11  =7.2.13  =7.2.14
          =7.2.16  =7.2.17  =7.2.19  =7.2.21  =7.2.22  =7.2.23
          =7.2.24  =7.2.26  =stable ]
 17  lamp-mariadb10.2-php7.2  available    \
        [ =10.2.10_7.2.0  =10.2.10_7.2.4  =10.2.10_7.2.5
          =10.2.10_7.2.8  =10.2.10_7.2.11  =10.2.10_7.2.13
          =10.2.10_7.2.14  =10.2.10_7.2.16  =10.2.10_7.2.17
          =10.2.10_7.2.19  =10.2.10_7.2.22  =10.2.10_7.2.23
          =10.2.10_7.2.24  =stable ]
 18  libreoffice              available    \
        [ =5.0.6.2_15  =5.3.6.1  =stable ]
 19  gimp                     available    [ =2.8.22 ]
 20  docker=latest            enabled      \
        [ =17.12.1  =18.03.1  =18.06.1  =18.09.9  =stable ]
 21  mate-desktop1.x          available    \
        [ =1.19.0  =1.20.0  =stable ]
 22  GraphicsMagick1.3        available    \
        [ =1.3.29  =1.3.32  =1.3.34  =stable ]
 23  tomcat8.5                available    \
        [ =8.5.31  =8.5.32  =8.5.38  =8.5.40  =8.5.42  =8.5.50
          =stable ]
 24  epel=latest              enabled      [ =7.11  =stable ]
 25  testing                  available    [ =1.0  =stable ]
 26  ecs                      available    [ =stable ]
 27  corretto8                available    \
        [ =1.8.0_192  =1.8.0_202  =1.8.0_212  =1.8.0_222  =1.8.0_232
          =1.8.0_242  =stable ]
 28  firecracker              available    [ =0.11  =stable ]
 29  golang1.11               available    \
        [ =1.11.3  =1.11.11  =1.11.13  =stable ]
 30  squid4                   available    [ =4  =stable ]
 31  php7.3                   available    \
        [ =7.3.2  =7.3.3  =7.3.4  =7.3.6  =7.3.8  =7.3.9  =7.3.10
          =7.3.11  =7.3.13  =stable ]
 32  lustre2.10               available    \
        [ =2.10.5  =2.10.8  =stable ]
 33  java-openjdk11           available    [ =11  =stable ]
 34  lynis                    available    [ =stable ]
 35  kernel-ng                available    [ =stable ]
 36  BCC                      available    [ =0.x  =stable ]
 37  mono                     available    [ =5.x  =stable ]
 38  nginx1                   available    [ =stable ]
 39  ruby2.6                  available    [ =2.6  =stable ]
 40  mock                     available    [ =stable ]
 41  postgresql11             available    [ =11  =stable ]
 42  php7.4                   available    [ =stable ]
 43  livepatch                available    [ =stable ]
 44  python3.8                available    [ =stable ]
 45  haproxy2                 available    [ =stable ]

よし!できた!これでインストールだ!と思ったのですが

[shimo0108 ~]$ sudo yum install nodejs npm --enablerepo=epel
読み込んだプラグイン:extras_suggestions, langpacks, priorities, update-motd
amzn2-core                                                                      | 3.7 kB  00:00:00     
amzn2extra-docker                                                               | 3.0 kB  00:00:00     
amzn2extra-epel                                                                 | 1.7 kB  00:00:00     


 One of the configured repositories failed (不明),
 and yum doesn't have enough cached data to continue. At this point the only
 safe thing yum can do is fail. There are a few ways to work "fix" this:

     1. Contact the upstream for the repository and get them to fix the problem.

     2. Reconfigure the baseurl/etc. for the repository, to point to a working
        upstream. This is most often useful if you are using a newer
        distribution release than is supported by the repository (and the
        packages for the previous distribution release still work).

     3. Run the command with the repository temporarily disabled
            yum --disablerepo=<repoid> ...

     4. Disable the repository permanently, so yum won't use it by default. Yum
        will then just ignore the repository until you permanently enable it
        again or use --enablerepo for temporary usage:

            yum-config-manager --disable <repoid>
        or
            subscription-manager repos --disable=<repoid>

     5. Configure the failing repository to be skipped, if it is unavailable.
        Note that yum will try to contact the repo. when it runs most commands,
        so will have to try and fail each time (and thus. yum will be be much
        slower). If it is a very temporary problem though, this is often a nice
        compromise:

            yum-config-manager --save --setopt=<repoid>.skip_if_unavailable=true

Cannot retrieve metalink for repository: epel/x86_64. Please verify its path and try again

とまたまたエラー。
yumで行うコマンドがすべて止まってしまう。。(泣)

[shimo0108@ ~]$ sudo yum install nodejs npm --enablerepo=epel
読み込んだプラグイン:extras_suggestions, langpacks, priorities, update-motd
amzn2-core                                                                   
| 3.7 kB  00:00:00     
amzn2extra-docker                                                               
| 3.0 kB  00:00:00     
amzn2extra-epel                                                                 
| 1.7 kB  00:00:00 
.
.

ここまで読み込んで必ず止まります。1日位格闘してエラーログに

Cannot retrieve metalink for repository: epel/x86_64. Please verify its path and try again

を発見。リポジトリのメタリンクを取得できませんと。言うことです。
調べた結果
CentOS6.xでEPELリポジトリ入れたときのエラー対処
https://qiita.com/maruware/items/eb659266a45021cf486c
という記事を発見。どうやらEPELリポジトリがSSL3.0を使っているのが原因とのことで、記事の通り以下を実行してhttps→httpに変更することで解決するらしい。

[shimo0108@ ~]$ sudo vi /etc/yum.repos.d/epel.repo

ここの/epel.repoをvimにて確認。

[epel]
name=Extra Packages for Enterprise Linux 7 - $basearch
#baseurl=https://download.fedoraproject.org/pub/epel/7/$basearch
metalink=https://mirrors.fedoraproject.org/metalink?repo=epel-7&arch=$basearch
failovermethod=priority
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-7

たしかに

metalink=https://mirrors.fedoraproject.org/metalink?repo=epel-7&arch=$basearch
#httpsになってる。。。
↓
[epel]
name=Extra Packages for Enterprise Linux 7 - $basearch
#baseurl=http://download.fedoraproject.org/pub/epel/7/$basearch
metalink=http://mirrors.fedoraproject.org/metalink?repo=epel-7&arch=$basearch
failovermethod=priority
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-7

#  https//→http//へ変更しました。

再び
sudo yum install nodejs npm --enablerepo=epel
を実行。
今度は成功しました!!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CLIでS3のファイルをアップロードしよう

はじめに

S3をマネジメントコンソール画面で作ることはできるが、CLIで操作して作成したことがないため、今回挑戦してみた。

手順

AWS CLIの確認

AWS CLIがインストールされているか下記のコマンドで確認。

aws --version

S3の権限設定

IAM>ロール>権限を設定したいロールをクリック。
「ポリシーをアタッチします」ボタンを押す。
スクリーンショット 2020-08-03 16.00.51.png
ポリシーのフィルタでs3と入力し、「AmazonFullAccess」を選択。ポリシーのアタッチを押す。
スクリーンショット 2020-08-03 16.04.37.png
アタッチされました、と表示される。
スクリーンショット 2020-08-03 16.09.14.png

デフォルトリージョンを設定

$ aws configure
AWS Access Key ID [None]: 
AWS Secret Access Key [None]:
Default region name [None]: ap-northeast-1
Default output format [None]: json

「AWS Access Key ID [None]:」と「AWS Secret Access Key [None]:」には何も指定しない。

S3バケットを作成

$ aws mb s3://hoge ※hogeの部分は重複負荷なので、一意になるようにする。
$aws s3 ls ※バゲットが作成できたか確認。

マネジメントコンソールでも確認できる。

S3バケットにファイルをアップロード

インターネット上で任意の画像をアップロードする。
Chromeブラウザの場合は画像を右クリックして、「画像アドレスをコピー」を選択。
スクリーンショット 2020-08-03 16.19.00.png
作業をするためのworkディレクトリをホームディレクトリに作成。
workディレクトリにwgetコマンドで画像アドレスを引数に指定して、ダウンロード。

$ mkdir ~/work
$ cd ~/work
$ wget https://hogehoge/hogehoge.png

s3 lsコマンドをバケットを指定して、実行。画像があるか確認する。

$ aws s3 ls s3://hoge

マネジメントコンソールでも画像があるか確認可能。

下記コマンドでCLIからパブリックなアクセス権限を設定。

$ aws s3api put-object-acl --acl public-read --bucket ファイル名 --key 画像名(hoge.png)

アクセスコントロールリストで許可設定があるか確認する。

$ aws s3api get-object-acl --bucket  ファイル名 --key 画像名(hoge.png)

マネジメントコンソールでオブジェクトURLをクリックして、画像が開けるか確認。
上記のような手順を踏むと、画像が表示される。

もしオブジェクトURLを押しても「AccessDenied」となって画像が表示されなかったら、該当オブジェクトの概要の「公開する」ボタンをおせば、表示される。
スクリーンショット 2020-08-03 19.59.35.png

参考
AWSではじめるLinux入門ガイド

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Linux基本コマンド

ls -la | grep 文字列・・指定された文字列が含まれる全てのファイルやディレクトリを詳細に表示する

※dから始まる→ディレクトリ
 -から始まる→ファイル
※.はフォルダー自身
 ..は一個上のフォルダー

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ls -la | grep 文字列

ls -la | grep 文字列・・指定された文字列が含まれる全てのファイルやディレクトリを詳細に表示する

結果(例)

drwx------@  3 ・・・
drwx------@  7 ・・・
drwx------+  4 ・・・
-rwx------@ 17・・・
drwxr-xr-x   3 ・・・

※dから始まる→ディレクトリ
 -から始まる→ファイル

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初心者向け】Linuxでのfor文の使い方(変数とか)

はじめに

この記事では私自身、for文を使おうとしてよくわからなかった部分(とくに変数の使い方あたり)について書いていこうと思います。

そもそも for文の使い方

for文の基本的な形
for 変数 in 値のリスト
do
処理
done

Linuxでfor文を使うには、以下のような方法があります。

1. シェルスクリプトとして読み込む

適当なエディタ(メモ帳とか)でファイルを作り(拡張子は .shにする)、上の形でfor文を書いて、Linux上で読み込みます。
ファイルの先頭行に#!/bin/bashという文字を書いておきます。

メモ帳などで作る(hoge.sh)
#!/bin/bash
for i in 1 2 3
do
echo $i        ## 1 2 3 を画面に出力する
done

作ったファイル(シェルスクリプト)を読み込むには、Linuxの画面上でファイル名を打ち込めばOKです。ただしファイルのパスを指定しなければいけないので「./ファイル名」( .現在のディレクトリを指す)とします。(※作ったファイルは事前に現在のディレクトリ内に配置しておきます)

出力結果
$ ./hoge.sh
1
2
3

2. コマンドライン上で書く(複数行)

上のようにメモ帳に書いた内容をLinuxの画面上にコピペしてもOKです。
その場合、行替えごとに>マークが自動でつきます。

コマンド&出力結果
$ for i in 1 2 3
> do
> echo $i
> done
1
2
3

3. コマンドライン上で書く(1行)

行替えする代わりに、forの行doneの手前;を付けることで、1行で書くこともできます。

コマンド&出力結果
$ for i in 1 2 3; do echo $i; done
1
2
3

参考:コマンドライン上でのbashを1行で (for文/if文)

以下の説明では、2の方法でfor文を使っていきます。

for文内での変数の使い方

基本的にはシェルスクリプトでの変数の使い方と同じです。
シェルスクリプト 変数」とかで調べるとたくさんサイトがあるので、ぜひ調べてみてください!

初心者向けシェルスクリプトの基本コマンドの紹介
シェルスクリプトの基礎知識まとめ
変数の利用 - Linux Mintのメモ

簡単にいくつかのケースを説明したいと思います。

1. 「$変数名」で値を使用する

変数 i の値は、$i で使用することができます。(上の例参照)

2. 変数に文字列が隣接する場合は「${変数名}」とする

$i と文字列が連続している場合には、${i}とする必要があります。

$ for i in 1 2 3
> do
> echo $ixxx
> echo ${i}xxx
> done
             ## "ixxx" という変数は存在しないので、何も出力されない。
1xxx         ## "変数iの値(=1)" + "xxx" が出力される。

2xxx

3xxx

3. 変数は ''ではなく""で囲む

ダブルクォート  "" ... 変数の中身を読み込む(ex. "$i" -> 1)
シングルクォート '' ... 文字列をそのまま表示(ex. '$i' ->  $i という文字列)
バッククォート  `` ... コマンドとして読み込む(ex. `cat test.txt` -> test.txtの中身を出力)

Linuxでは'' ""に上記のような差があるので、変数をクォーテーションで囲むときは必ず""を使用します

$ cat test01.txt
1xxxxxxxxxx
xxx2xxxxxxx
xxxxx3xxxxx
xxxxxxxx4xx
xxxxxxxxxx5

## 1 4 5 を含む行だけ抽出
$ for i in 1 4 5
> do
> cat test01.txt | grep "$i"
> done
1xxxxxxxxxx
xxxxxxxx4xx
xxxxxxxxxx5

## $i という文字列を含む行を抽出(何も出力されない)
$ for i in 1 4 5
> do
> cat test01.txt | grep '$i'
> done

参考:【bash】 「\"」、「\'」、「`」の違い - どちらかというとごはん派

値のリストの書き方

ざっくりリストアップするとこんな感じです(他にもいろいろあると思います)。

## i を 1 2 3 4 5 で動かす
for i in 1 2 3 4 5
for i in {1..5}
for i in `seq 1 5`
for i in `seq 5`
for ((i=1; i<=5; i++))

## i を 2 ずつ動かす ( 1 3 5 7 9 )
for ((i=1; i<=10; i=i+2))         ## (最初の値; iの条件; iの増え方(or減り方))
for ((i=1; i<=10; i+=2))

## i に文字を代入
for i in aa bb cc

## ファイル内の各行を読み込む (空白でもループが区切られるので注意; 後述)
for i in `cat test.txt`

## ディレクトリ内のファイルを指定
for i in *.txt
for i in `ls`

参考:
【 for 】コマンド(応用編その2)――コマンドの実行結果と組み合わせて、繰り返し処理を行う
【 for 】コマンド(応用編その3)――回数を指定して繰り返し処理を行う

おまけ:ファイル内の各行を読み込む(while read line)

for文よりwhile文で読み込むやり方の方がメジャーなようです(→追記参照)。
方法は何パターンかありますが、読み込むファイルの最終行で改行されていない場合には、方法によっては最終行だけ読み込まれない場合があるので、注意が必要です。

参考:
コピペ可|bash/while read lineで行単位で処理:5パターン
BASHのwhile readで最終行が処理されない問題の解決方法

調べた限り、ヒアドキュメントで渡す方法(下の方法)ではその問題を回避できるので、個人的にはこの方法が良いのではないかと思います。

$ cat test02.txt
1
2
3
4
5    ## 改行されていない

## 最終行が読み込まれない
$ cat test02.txt | while read line
> do
> echo line : $line
> done
line : 1
line : 2
line : 3
line : 4

## 最終行まで読み込まれる
$ while read line
> do
> echo line : $line
> done << Hear
> `cat test02.txt`
> Hear
line : 1
line : 2
line : 3
line : 4
line : 5

追記

上のリンクの説明にもある通り、for文の場合、改行だけでなく空白でもループが区切られてしまうので、各行ごとに処理を行いたい場合にはwhile read lineを使った方が汎用性は高いです。

$ cat test02.txt
1 x
2xx
3
4
5

## forは空白でも区切られる
$ for i in `cat test02.txt`
> do
> echo line : $i
> done
line : 1
line : x
line : 2xx
line : 3
line : 4
line : 5

## whileは空白で区切られない
$ while read line
> do
> echo line : $line
> done << Hear
> `cat test02.txt`
> Hear
line : 1 x
line : 2xx
line : 3
line : 4
line : 5
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Linuxカーネルに入門したいのだけどlist head構造体って何するもの?

Table of Contents

  1. はじめに
    1. ソースコードの入手
    2. 最初の一歩?
  2. list head構造体
    1. 背景
    2. list head構造体の定義
    3. Linuxカーネルでの使われ方
    4. ペアレント構造体へのポインタを得る
  3. まとめ
  4. 参考文献

はじめに

Linuxユーザーのエンジニアであれば、Linuxがどのように動くのかに興味ありますよね。いつかはLinuxカーネルについて勉強してみたいと思っている方も少なくないのではないでしょうか。

この記事では、Linuxカーネルに入門しようとしている初心者が、割と早いうちに途方に暮れると思われる壁「list_head構造体」について見ていきたいと思います。

この記事は私のブログ https://achiwa912.github.io/ にも載せました。

ソースコードの入手

まずはLinuxカーネルのソースコードを入手しましょう。

Linuxカーネルの開発はずっと続いているので、書籍などで引用されているコードはすでに古くなっています。折角なので、最新版を入手したいですよね。
Linuxカーネルのソースコードをgit cloneして持ってきましょう。

git clone http://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git

20−30分くらいかかります。放置して待ちましょう。

~/git % git clone http://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git
Cloning into 'linux-stable'...
warning: redirecting to https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git/
remote: Enumerating objects: 1189260, done.
remote: Counting objects: 100% (1189260/1189260), done.
remote: Compressing objects: 100% (165947/165947), done.
remote: Total 8680156 (delta 1022459), reused 1186934 (delta 1020762), pack-reused 7490896
Receiving objects: 100% (8680156/8680156), 1.57 GiB | 3.01 MiB/s, done.
Resolving deltas: 100% (7328421/7328421), done.
Updating files: 100% (69365/69365), done.
warning: the following paths have collided (e.g. case-sensitive paths
on a case-insensitive filesystem) and only one from the same
colliding group is in the working tree:

  'include/uapi/linux/netfilter/xt_CONNMARK.h'
  'include/uapi/linux/netfilter/xt_connmark.h'
  'include/uapi/linux/netfilter/xt_DSCP.h'
  'include/uapi/linux/netfilter/xt_dscp.h'
  'include/uapi/linux/netfilter/xt_MARK.h'
  'include/uapi/linux/netfilter/xt_mark.h'
  'include/uapi/linux/netfilter/xt_RATEEST.h'
  'include/uapi/linux/netfilter/xt_rateest.h'
  'include/uapi/linux/netfilter/xt_TCPMSS.h'
  'include/uapi/linux/netfilter/xt_tcpmss.h'
  'include/uapi/linux/netfilter_ipv4/ipt_ECN.h'
  'include/uapi/linux/netfilter_ipv4/ipt_ecn.h'
  'include/uapi/linux/netfilter_ipv4/ipt_TTL.h'
  'include/uapi/linux/netfilter_ipv4/ipt_ttl.h'
  'include/uapi/linux/netfilter_ipv6/ip6t_HL.h'
  'include/uapi/linux/netfilter_ipv6/ip6t_hl.h'
  'net/netfilter/xt_DSCP.c'
  'net/netfilter/xt_dscp.c'
  'net/netfilter/xt_HL.c'
  'net/netfilter/xt_hl.c'
  'net/netfilter/xt_RATEEST.c'
  'net/netfilter/xt_rateest.c'
  'net/netfilter/xt_TCPMSS.c'
  'net/netfilter/xt_tcpmss.c'
  'tools/memory-model/litmus-tests/Z6.0+pooncelock+poonceLock+pombonce.litmus'
  'tools/memory-model/litmus-tests/Z6.0+pooncelock+pooncelock+pombonce.litmus'

おめでとうございます。これで、Linuxカーネルのソースコードを一式入手することができました。意外と簡単ですね。

最初の一歩?

Linuxのカーネルは巨大です。一体、どこから見始めればよいのか検討もつきません。普通は参考文献にあるようなLinuxカーネル本をガイドにするのがよいと思います。ところで、私の場合はVFSに興味があります。inodeとかsuperblockとかのあれです。若干いきなりすぎる感もありますが、superblockのデータ構造を見てみましょう。
linux-stable/include/linux/fs.hにありました。

struct super_block {
    struct list_head        s_list;         /* Keep this first */
    dev_t                   s_dev;          /* search index; _not_ kdev_t */
    unsigned char           s_blocksize_bits;
    unsigned long           s_blocksize;
    loff_t                  s_maxbytes;     /* Max file size */
    struct file_system_type *s_type;
    const struct super_operations   *s_op;
    const struct dquot_operations   *dq_op;
    const struct quotactl_ops       *s_qcop;
    const struct export_operations *s_export_op;
    unsigned long           s_flags;
    unsigned long           s_iflags;       /* internal SB_I_* flags */
    unsigned long           s_magic;
    struct dentry           *s_root;
    struct rw_semaphore     s_umount;
    int                     s_count;
    atomic_t                s_active;
<snip>

これが、superblockのデータ構造です。中身はさっぱりわかりませんが。。。
構造体の中の1行目に着目します。

struct list_head        s_list;         /* Keep this first */

これです。list head構造体。こいつが、カーネルのソースコードを少しでも読もうとする私のような初心者を突き放す、手強いやつなのです。しかも、やたらとたくさん出てきます。今回の記事では、これが何を意味するのかについて見ていきたいと思います。

list head構造体

背景

LinuxカーネルはC言語で書かれています。C言語には、よりモダンなプログラミング言語と違って、サポートするデータ構造は貧弱で、オブジェクト指向の仕組みも入っていません。例えば、Python等にあるリスト構造(['abc', 'def']みたいなやつ)はとても便利で、無くてはかったるくてプログラムなど書いていられないほどですが、C言語にリストはありません。同様にC言語にはクラスもありません。あるのは構造体のみ。

Linuxカーネル開発者達はこれらのモダンなデータ構造を、ユニークなやり方で実現しています。その一つが、リスト構造を実現するlist head構造体です。

list head構造体の定義

では、list head構造体の定義を見てみましょう。
linux-stable/include/linux/types.hにあります。

struct list_head {
        struct list_head *next, *prev;
};

拍子抜けするほど単純ですね。前方、後方の、自身と同じlist head構造体へのポインタが入っているだけでした。なるほど、双方向リンクトリストなのですね。こういったやつです。(厳密には、循環双方向リンクトリストです)

+------+     +------+------+------+     +------+-----+------+     +------+
| null | <-> | prev |  ... | next | <-> | prev | ... | next | <-> | null |
+------+     +------+------+------+     +------+-----+------+     +------+

Linuxカーネルでの使われ方

いや、ちょっと待ってください。リストって、前後のポインタだけ入っていても意味が無いです。リストを構成する各ノードのデータが入っていないと。例えばinodeをリンクトリストで持つ場合、inodeはinode自身のデータをたくさん持っているはずです。ファイル名とか、オーナーとか、パーミッションとか。上の図で言うと・・・の部分です。

実は、list head構造体は、それを他の構造体に埋め込むことで、埋め込まれたペアレント構造体をリンクトリスト化できる便利なやつなのです。すごい!発想の転換です。

そう言えば、superblockの構造体は、list head構造体をメンバーとして持っていました。

struct super_block {
    struct list_head        s_list;         /* Keep this first */
    dev_t                   s_dev;          /* search index; _not_ kdev_t */
    unsigned char           s_blocksize_bits;
<snip>

こうすることで、superblockがリンクトリストのノードになっているのです。

更に、いくつものlist_head構造体を埋め込むことで、複数のリンクトリストに同時に登録することも可能です。構造体の定義を見ただけで、これは何と何と何のリンクトリストに含まれるかもわかります(コメントが書いてあるので)。これはPythonのリンクには真似ができませんね。

ペアレント構造体へのポインタを得る

リストを持たないC言語でリンクトリストを実現するため、という目的はわかりましたが、一つ問題が残っています。リストヘッド構造体は、同じリストヘッド構造体同士を結びつけるだけなのですが、本当に欲しいのはそれを埋め込んだペアレント構造体へのポインタです。ペアレント構造体をリンクトリスト化したいのですから。

それをするための関数が定義されています。

/**
 * list_entry - get the struct for this entry
 * @ptr:        the &struct list_head pointer.
 * @type:       the type of the struct this is embedded in.
 * @member:     the name of the list_head within the struct.
 */
#define list_entry(ptr, type, member) \
    container_of(ptr, type, member)

list_entry()関数です。この3つの引数は、

  • ptr: このlist_head構造体へのポインタ
  • type: list_headを埋め込んであるペアレント構造体のタイプ(上の例ではsuper_block)
  • member: このlist_head構造体の、ペアレント構造体内のメンバー名(上の例ではs_list)

であり、ペアレント構造体へのポインタを返します。

よかった、よかった。ですが、list_entry()関数の定義内容が気になります。
container_of()って何でしょうか。
grepで探したところ、linux-stable/include/linux/kernel.hに定義がありました。

/**
 * container_of - cast a member of a structure out to the containing structure
 * @ptr:        the pointer to the member.
 * @type:       the type of the container struct this is embedded in.
 * @member:     the name of the member within the struct.
 *
 */
#define container_of(ptr, type, member) ({                              \
    void *__mptr = (void *)(ptr);                                   \
    BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) &&   \
             !__same_type(*(ptr), void),                    \
             "pointer type mismatch in container_of()");    \
    ((type *)(__mptr - offsetof(type, member))); })

ポイントだけ抜き出します。

#define container_of(ptr, type, member) ({                              \
        void *__mptr = (void *)(ptr);                                   \
        ((type *)(__mptr - offsetof(type, member))); })

まずは、voidへのポインタ__mptrに、list_head構造体へのポインタptrをvoidへのポインタにキャストして代入しています。list_head構造体へのポインタのままだと使えないですからね。

次の行で、__mptrを、offsetof(type, member)分前にずらしているようです。offsetof(type, member)は上の例の場合、super_block構造体の中でのメンバーstruct list_head s_listのオフセットです。つまり、ペアレントであるsuper_block構造体へのポインタに変換しているのでした。そしてこれを、super_block構造体へのポインタにキャストしています。

まとめると、

  1. list_head構造体をvoidへのポインタにキャストして扱いやすくする
  2. 作ったポインタを手前にずらして、ペアレント構造体の先頭を指すようにする
  3. 最後に、ペアレント構造体へのポインタにキャストする

ということでした。

なお、container_ofについては、参考文献のLinux Kernel Developmentの中では次のように定義されていました。

#define container_of(ptr, type, member) ({ \
    const typeof( ((type *)0)->member ) *__mptr = (ptr); \
    (type *)( (char *)__mptr - offsetof(type,member) );})

これを見て、「((type *)0)->memberって何だ? これは一体何をしているんだ???」と相当悩んだことがこの記事を書く動機だったのですが、最新のソースでは、若干わかりやすくなっていました。

まとめ

Linuxカーネルで定義するデータ構造においt、list_head構造体が埋め込まれた構造体があったら、それは何かのリンクトリストに含まれるということがわかりましたね。ソースのコメントを読めば、大抵は何のリンクトリストかが書いてあります。

list_entry()関数を使って、list_head構造体のポインタから、それが埋め込まれたペアレント構造体へのポインタに変換できます。そして、Linuxカーネルでのlist_entry() - contaier_of()の実装を少し見てみました。

参考文献

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む