- 投稿日:2020-02-19T23:36:42+09:00
ubuntuとは
Ubuntuとは
Linuxディストリビューションのこと
Linux:
広義ではOSの名前(Windows的な)。狭義では、OSの中核(カーネル)のこと。ディストリビューション:
OSとしてのLinux(つまり広義)。狭義の、カーネルとしてのLinuxだけではPCは何もできないので、色々な機能を持ったソフトウェア(例えば電卓とか)を盛り込む必要がある。
つまり、Linux(狭義) + 色んなソフトウェア = ディストリビューション(=広義のLinux)ってこと。
そしてディストリビューションって言葉が出てきたら、ほぼLinuxの話だと思っていいみたい。だから、
Linuxディストリビューション:
カーネルとしてのLinuxに他の仕事をするソフトウェアを盛り込んだ、OSとしてのLinux(広義)のこと。
で、Linuxディストリビューションというのはいくつも種類がある。まとめ
まとめるとUbuntuは…
「カーネルとしてのLinux(狭義)」と「色んな機能を持ったソフトウェア」をまとめた広義の方のOSとしてのLinux(=Linuxディストリビューション)の一つこと。
むずかしTT ブチギレみけんコネコネ。
- 投稿日:2020-02-19T22:58:59+09:00
archlinuxのインストール方法
biosモードで起動している場合の方法です。
usbメディアを作る
https://www.archlinux.jp/download/
から落としてきたisoファイルを適当なツールでUSBに焼く。パーティションを作る
fdiskを使ってパーティションを1つ作る。
パーティションをフォーマットする。# mkfs.ext4 /dev/sda1パーティションをマウントする。
# mount /dev/sda1 /mntスワップ領域はあとからスワップファイルで設定できるので今は作らない。
ベースシステムをインストール
/etc/pacman.d/mirrorlist を編集して日本サーバーの行を一番上に移動する。
pacstrapを使ってマウントしたパーティションにベースシステムをインストールする。# pacstrap /mnt base base-devel linux linux-headers linux-firmwarefstabを生成する
# genfstab -U /mnt >> /mnt/etc/fstabchroot
chrootする
# arch-chroot /mntwheelグループのユーザーがsudoコマンドを実行できるようにする
# visudoを実行し
%wheel ALL=(ALL) ALLのコメントアウトを外す。
rootユーザーのパスワードを設定する
# passwd一般ユーザーを追加し、wheelグループに追加する。
# useradd -m ユーザー名 # usermod -aG wheel ユーザー名 # passwd ユーザー名必要なパッケージを入れる。
ドライバーやエディター、ttyは自分の環境や好みに応じて変えてください# pacman -S sudo nvidia networkmanager plasma vim alacrittysddmとnetworkmanagerを有効化
# systemctl enable sddm # systemctl enable NetworkManagergrubをインストール
# grub-install --target=i386-pc /dev/sda # grub-mkconfig -o /boot/grub/grub.cfgchrootを抜ける
# exit電源を切ってUSBメモリを抜いてから起動する。
タイムゾーンなどの設定
まだタイムゾーンやローケーションの設定をしていないのでkdeの設定から設定する。
日本語で使う場合は日本語フォントも入れる。(ファイルを落として開けばwindowsと同じように簡単にインストールできる。)
- 投稿日:2020-02-19T20:14:48+09:00
Rust で vmlinux を起動できる x86 ブートローダーを作ってみた話
Rust を勉強し始めたので冬休みの間に Linux の boot protocol を喋る x86ブートローダー(自称:Krabs)を作ってみました。この記事では、開発に至った動機や、作成した Krabs の特徴とか仕組み、開発中におきた嬉しかったことなどについて書きたいと思います。
Krabs とは
Krabs は、Rustで書かれた x86/x86_64(Legacy BIOS) 向けの4段ロケット構成のチェインローダーです。
bzip2 で圧縮された ELF 形式のカーネルを起動できます。bzip2 圧縮されたイメージを解凍して、次に展開してでてきた ELF イメージを再配置してからの、カーネルの起動となります。
内部では libbzip2 の C ライブラリを利用していますが、それ以外は全て Rust で記述されています。GitHub - ellbrid/krabs: An x86 bootloader written in Rust.
以下の特徴があります。
- legacy BIOS に対応
- 64bit ロングモード, 32 bit プロテクトモードの両方に対応
- 最小限の x86/x86_64 Linux boot protocol に対応
- Linux boot protocol に従って kernel コマンドラインを指定し、OS 起動時の動作を制御可能
- Linux boot protocol に従って initramsfs/initrd などのモジュールをロードできる
- マルチブート仕様には対応していない。
kernel command line と initrd を活用する 64bit vmlinux の起動例は以下になります。
./tools/build.sh -k vmlinux -i initramfs.cpio.gz -c "clocksource=tsc" disk.img qemu-system-x86_64 --hda disk.img -m 1GKrabs を作ろうと思った動機
Rust の練習と Linux の習熟が目的です。OSスタックより下のレベルでのcodingもRust を使用することにより、よりモダンにできるのではないかと考えました。また。Linuxカーネル起動までの過程から、必要最小限のエッセンス部分だけを抽出し、最終的に自分にとってブラックボックスが一切存在しないブート環境を作り上げてみたいと言う思いもありました。基本的には、以下の混迷する部分、詰まりがちな部分を取り除くことを目標としました。
- GRUB/2ブートローダーや bzImage など、チェインローダーの仕組みや技法が難解
- また、そこでのアセンブリ<->Cとの往復は、素人目には超スパゲッティ状態
- 起動コードの読破は結構時間と努力が必要。かりに読破できたとしても、得られるものがあるのかどうか..
- Rust は成果物が大きくなりがちでブートローダーの記述には向いていないと言われているが、本当か?
以上を踏まえて、ブートローダーの読解や bzImage コードの追跡はいさぎよく諦め、自ら Rust でブートローダーを書き下ろすことにしました。
仕組み
Linux カーネル起動機構
Linux カーネル起動機構を bzImage や GRUB ブートローダーから眺めてしまうと難しく感じてしまいますが、実態は意外にも単純です。基本的なことは、2点です。圧縮イメージを展開し、ELF 形式のイメージを取り出したら、それをプログラムヘッダーに従って再配置すること、そして、The Linux/x86 Boot Protocol に従って初期化処理を行い、パラメーターの設定をすること。これだけです。
あっけないほど簡単に思えますが、具体的には、以下の4種類の初期化処理を行います。
ハードウェア初期化:
- キーボードリピート頻度の設定(最大値)
- 割り込みの禁止およびすべての割り込みレベルのマスク
- 割り込みディスクプリタ(IDT), セグメントディスクプリタ(GDT)の設定
- すべてのセレクタ(CS, DS, ES, FS, GS)は4Gバイトフラットのリニアアドレス空間を参照
- アドレスバスの32bit化(A20ラインの有効化)
- プロテクトモードへの移行
- ターゲットがELF64の場合、4Gブートページテーブルを設定し、ロングモードに移行
ソフトウェア初期化:
- BIOS 呼び出しによるシステム搭載メモリ容量の取得
カーネルへの情報伝達:
- カーネルパラメータ用にZero Pageを設定し、OSに送信
イメージの配置:
- ターゲットはELFファイルだが、Krabsはbzip2圧縮後にそれを使用する。したがって、2段階の再配置が必要:1つはbzip2解凍で、もう1つはELF再配置
- initrd/initramfsなどのモジュールをロードして配置する
上記の処理を行うのに Krabs は4つのステージに分かれたプログラムで対応します。
Krabsの構造と概要
- stage1:
stage1はブートセクターに書き込まれた446バイトのプログラムです。セグメントレジスタ(CS
、DS
、ES
、SS
)は0x07C0
に設定され、スタックポインター(ESP
)は0xFFF0
に初期化されます。その後、stage2をアドレス0x07C0:0x0200
にロードし、アドレス0x07C0:0x0280
にジャンプします。 stage1の末端2byteには、stage2プログラムのセクター長(512バイト単位)を格納するための領域があります。Rust でも 446 byte に収まる1stステージローダーが書けるのです!- stage2:
stage3をアドレス0x07C0:0x6000
にロードします。bzip2圧縮されたカーネルイメージは拡張メモリ領域のアドレス0x0350_0000
にロードされ、initrdは0x0560_0000
にロードされます。これらのファイルの転送には、アドレス0x07C0:0xEE00
から4Kバイトのトラックバッファーを使用しました。ディスクから読み取ったものを一時ここに格納し、さらにINT 15h
BIOSファンクション0x87h
を使用して適切なアドレスに転送します。 stage3、initrd、および圧縮カーネルイメージの読み込みが完了したら、アドレス0x07C0:0x6000
にジャンプします。なお、カーネルコマンドラインは、アドレス0x280
から120バイトの領域に保持されています。- stage3 + stage4:
Stage3とStage4は、bss領域を使用する可能性があるため、.bss
セクションのゼロクリアをサポートする必要があります。一連のハードウェアおよびソフトウェアの初期化の後、Zero Page 情報を0x07C0:0x0000
〜0x07C0:0x0FFF
に準備します。 A20 line を有効にし、アドレスバスを32ビットに変更して、保護モードに移行します。bzip2 解凍関数を呼び出し、bzip2圧縮されたELFカーネルイメージを拡張メモリアドレス0x100000
以降に復元します。そして、ELF32 / ELF64ファイルをパースして、ロードします。もし、ターゲットがELF64だった場合には、4Gブートページテーブルを設定し、ロングモードに移行します。最後に、エントリポイントにジャンプしてカーネルを起動します。このとき、下位メモリに用意された Zero Page 情報の物理アドレス(0x00007C00)をESIまたはRSIレジスタに設定しておきます。- plankton:
planktonはstage1 ~ stage4 に共通しているライブラリです。Disk 構造
KrabsはHDDとSSDに対応していますが、必ず MBR を持つ必要があります。また、いずれかのパーティションは必ず boot flag が設定されている必要があります。stage3,4, kernel, initrd は bootflag のついたブートパーティションに格納されます。
補足
ところで、なぜ legacy BIOS に対応しているのか気になっている方がいるかもしれないので、こちらについて補足させてください。以下の3点の理由から今回は legacy BIOSのみサポートしています。
- このブートローダーはもともとThinkPad 600Xと言う古の?PCで使うことを目的として作ってた。
- 現時点においては、UEFI よりもレガシーBIOS に対応したほうが幅広い環境で使える。
- 自分の PC 以外では主にクラウド環境で使用することを目的としている。クラウド環境では レガシーBIOS が主流で、またこれをUEFIに置き換えるメリットもなさそうだったので、当分生存できそう。
Rust で書いてみた感想
Rust は低レベルなコードを書く上で C よりも圧倒的に楽。と言うのが個人的な感想です。
コンパイルが通った時の安心感がすごい
例え問題が起きても本当に
unsafe
部分を疑うだけで済む。今回はこれ本当だった。パッケージ、モジュールの考え方が最高
Cみたいにオブジェクトファイルどれとどれリンクするんだっけってイライラしなくていい。
モダンなコードをかけて楽
no_std の低レベルのコードも比較的モダンにかける。
また、あれが使えないとかこれが使えないとかそんなに悩むことはない。
体感ですが、開発速度はCよりも早いかも?
何より、Rust は書いてて楽しいです。
型を意識して書くのいいです。また、いろいろな機能を使うのも楽しいです。Rust でもチェインローダーが書ける
かけます。16bit/32bit もあまり不自由なくチェインローダーがかけます。
チェインローダーはそのロケット構造上、次のステージに行くために必ず unsafe な部分を記述しなければなりませんが、その unsafe な部分には、Cのときによく使う技法がそのまま使えるのもいいなと思いました。
ただし、ブートセクタ内部ではマクロとクロージャは要注意です。サイズが爆発しやすい気がします。Cの資産の活用と提供
FFIによりCの資産をあまり意識せずに活用できるのは大変素晴らしいと思います。
今回libbzip2を内部で使用していますが、Rust から簡単に使うことができました。
また、逆にCにRustの資産を提供することも容易かったです。libbzip2 の使用のためにはmalloc
が必要ですが、これについて Rust で簡単に実装したものを C 側に提供できました。ハマったところ
ページテーブルを設定するのに、アラインメントをリンカースクリプトやstructの属性で設定しようとしましたが、いずれもうまくいかず。。。(他のデータ構造が壊れてしまっていたように見えた)。Rust のアラインメント、何かおかしいのかと思いつつそのまま放置。結局、ページテーブルを設定したい領域を手動で確保して、そこのアドレスをベタ打ちすることで対応しました。例
超大物エンジニアが助けてくれた!
Krabs を作成して単純な ELF 形式のカーネルを起動できるようになってから しばらくは、なぜか vmlinux を正しく起動できず、開発が少しの期間停止していました。その間、READMEを英語で書いたり、単純な動作例をテストしたり、long mode に対応したりして、twitter 上で英語で宣伝を行っていました。
なんでうまくいかないんだろうと毎日悩んでいて、bzImage のソースを読み込んでいたある日、とても嬉しいことが起きました。何気なく日本語で呟いたツイートに、なんと AWS の超大物エンジニア @_msw_ と x86/x86-64 Linux kernel treeの長年のメンテナとして有名な@LinuxHPA(Hans Peter Anvin - Wikipedia) が反応してアドバイスくれたのです!思わず嬉しすぎてスクショしました。
このアドバイスのおかげで、問題は一気に解決し、また、マルチブート仕様には対応しない決意を新たにしました(もともともkernel内部にパラメータを埋め込むマルチブート仕様は僕はあまり好きじゃないです)。今回は日本語に反応してくれましたが、もともと英語で宣伝していたものにリアクション頂いてから繋がっていました。英語で発信しておくの大事だなと改めて思いました。
あと英語で呟くと、twitter で直接 DM をくれたり、リプライで応援メッセージを送ってもらえる頻度が上がる気がします。大変励みになります。嬉しかったメッセージを紹介します。Writing an OS in Rust の方もメッセージくれました。
i will study this to understand better low level programing and try "upgrading " templeOS
— rotten lung (@satanacio666) February 8, 2020Very cool seeing @rustlang being used to modernize the lower levels of the OS stack https://t.co/CpXXoWDt2t
— Dino A. Dai Zovi (@dinodaizovi) February 8, 2020Awesome! I will definitely check it out when I have some time.
— Philipp Oppermann (@phil_opp) February 4, 2020...これで終わるともったいないので、Krabs を体験していただくためにも、最後に 最小構成の Linux システムを構築してこれを Krabs で起動する例を扱いたいと思います。
Minimal な Linux を起動しよう
- ブートローダー作りたい人
- 最小構成のLinuxシステムをビルドしてみたい人
- 簡単なinitramfs作ってみたい人
とかの参考になるのではと思います。(以下、CentOS7で作業してます)
minimal な vmlinux を作ろう!
1: Linuxのソースを持ってきます。
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.5.2.tar.gz tar xf linux-5.5.2.tar.gz cd linux-5.5.22: Linuxのconfigを設定します。
make allnoconfig make menuconfig以下の設定を使用します。
64-bit kernel ---> yes General setup ---> Initial RAM filesystem and RAM disk (initramfs/initrd) support ---> yes General setup ---> Configure standard kernel features ---> Enable support for printk ---> yes Executable file formats / Emulations ---> Kernel support for ELF binaries ---> yes Executable file formats / Emulations ---> Kernel support for scripts starting with #! ---> yes Enable the block layer ---> yes Device Drivers ---> Generic Driver Options ---> Maintain a devtmpfs filesystem to mount at /dev ---> yes Device Drivers ---> Generic Driver Options ---> Automount devtmpfs at /dev, after the kernel mounted the rootfs ---> yes Device Drivers ---> Character devices ---> Enable TTY ---> yes Device Drivers ---> Character devices ---> Serial drivers ---> 8250/16550 and compatible serial support ---> yes Device Drivers ---> Character devices ---> Serial drivers ---> Console on 8250/16550 and compatible serial port ---> yes Device Drivers ---> Block devices ---> yes Device Drivers ---> PCI Support --> yes Device Drivers ---> Serial ATA and Parallel ATA drivers (libata) ---> yes Device Drivers ---> Serial ATA and Parallel ATA drivers (libata) ---> Intel ESB, ICH, PIIX3, PIIX4 PATA/SATA support ---> yes Device Drivers ---> Serial ATA and Parallel ATA drivers (libata) ---> Generic ATA support ---> yes Device Drivers ---> SCSI device support ---> SCSI disk support File systems ---> The Extended 4 (ext4) filesystem ---> yes File systems ---> Pseudo filesystems ---> /proc file system support ---> yes File systems ---> Pseudo filesystems ---> sysfs file system support ---> yesもしくは、上記をすでに設定してある my recommended config を用意してあるので、これを
.config
にコピーしてきます。wget https://raw.githubusercontent.com/ellbrid/krabs/master/resources/.config -O .config make menuconfig
3: vmlinuxをビルドします。
make vmlinux4: カレントディレクトリに
vmlinux
が出来上がっています。initramfsを作ろう!
1: まず
./src/initramfs
ディレクトリを作成してここに基本的なディレクトリを構築していきます。cd .. mkdir --parents src/initramfs/{bin,dev,etc,lib,lib64,mnt/root,proc,root,sbin,sys}2: 基本的なデバイスノードもコピーしていきます。
1. sudo cp --archive /dev/{null,console,tty,tty[0-4],sda,sda[1-8],mem,kmsg,random,urandom,zero} src/initramfs/dev/3: 動的ライブラリなど導入して環境を構築する代わりに、busyboxを使用します。
curl -L 'https://www.busybox.net/downloads/binaries/1.31.0-defconfig-multiarch-musl/busybox-x86_64' > src/initramfs/bin/busybox sudo chmod +x src/initramfs/bin/busybox ./src/initramfs/bin/busybox --list | sed 's:^:src/initramfs/bin/:' | xargs -n 1 ln -s busybox4:
init
スクリプトを用意します。cat >> src/initramfs/init << EOF #!/bin/sh mount -t devtmpfs devtmpfs /dev mount -t proc proc /proc mount -t sysfs sysfs /sys sleep 2 cat <<END Boot took $(cut -d' ' -f1 /proc/uptime) seconds _____ _ __ _ | __|___ ___ _| |_ _ | | |_|___ _ _ _ _ |__ | .'| | . | | | | |__| | | | |_'_| |_____|__,|_|_|___|_ | |_____|_|_|_|___|_,_| |___| Welcome to Sandy Linux END exec sh EOFsudo chmod +x src/initramfs/init
5: initramfsを作ります。
cd src/initramfs find . | cpio -o -H newc | gzip > ../../initramfs.cpio.gzdiskイメージを作ろう!
1: イメージファイルを
qemu-img
で作成します。dd
でもいいです。qemu-img create disk.img 512M2:
fdisk
でパーティションを作成します。1st partition:
Command (m for help): n Partition type: p primary (0 primary, 0 extended, 4 free) e extended Select (default p): p Partition number (1-4, default 1): 1 First sector (2048-1048575, default 2048): 2048 Last sector, +sectors or +size{K,M,G} (2048-1048575, default 1048575): 206848 Partition 1 of type Linux and of size 100 MiB is setブートフラグを第1パーティションに作ります:
Command (m for help): a Selected partition 12nd partition:
Command (m for help): n Partition type: p primary (1 primary, 0 extended, 3 free) e extended Select (default p): p Partition number (2-4, default 2): First sector (206849-1048575, default 208896): Using default value 208896 Last sector, +sectors or +size{K,M,G} (208896-1048575, default 1048575): Using default value 1048575 Partition 2 of type Linux and of size 410 MiB is setwrite out:
Command (m for help): w The partition table has been altered! Syncing disks.3: 第2パーティションにext4ファイルシステムを作る
$ sudo kpartx -av disk.img lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sr0 11:0 1 1024M 0 rom loop0 7:0 0 512M 0 loop ├─loop0p1 253:2 0 100M 0 part └─loop0p2 253:3 0 410M 0 part $ sudo mkfs.ext4 /dev/mapper/loop0p2 $ sudo kpartx -d disk.imgKrabsで起動しよう!
以下のコマンドを実行するだけです。これで vmlinux が bzip2 圧縮されてブートローダーとともに disk.img に書き込まれます。
$ pwd path/to/krabs $ ./tools/build.sh -k path/to/vmlinux -i path/to/initramfs.cpio.gz path/to/disk.imgそれでは起動しましょう!
$ qemu-system-x86_64 --hda disk.img -m 1Gあとがき
そういえば reddit のコメントで、なんで xz や gzip にしなかったのか聞かれました。あまり深く考えてませんでしたが、単に bzip2 を持ってくるのが簡単だったという理由があります。また、bzip2 を Rust に移植中のニュースを聞いていたのでそれが楽しみであるのも理由の一つです。
ただ、上記の Minimal Linux を起動する例をお試しいただいた方はお分かりいただけるかと思いますが、bzip2は大変遅いです。このため将来的には gzip もしくは xz に移行する可能性があります。ちなみに僕はスポンジボブが好きで、このブートローダーについてカーニさんとプランクトンから名前を拝借してます。僕の起動する Linux では GRUB じゃなくて Krabs を使うのが密かな目標です。(spongeと命名した自作OSも作り始める予定です)。
Amazon EC2 で、このブートローダーを使用するオリジナルのジョーク Linux AMI を作成することも目指してます(その名も Sandy Linux AMI 笑)。Rust製のcoreutilsに、init に Rust製のsystemdであるrustysdを使用しようかなと考えています。sshはこれでイケる? 夢が広がりますね。
あと、githubのstarはすごい嬉しいものであることがわかったので、これからどんどんstarしていこうと思いました。
最後まで読んでいただきありがとうございました。
いいね、コメント、アドバイス、issue、プルリクとかありましたら、ぜひお願いしますmm。
- 投稿日:2020-02-19T18:37:27+09:00
SDL_mixerを使ったプログラムでMIDIがならないときの確認点
SimutransというゲームではBGMがMIDIファイルになっており、そのBGMがならなくて困ったので解決策を探していたらLinuxでSDL_mixerを使っている際に共通する(と思われる)解決策で解決でき、また日本語情報が見つからなかったので共有します。
動作環境
上記の通り、今回は一つのゲームでしか試していません。そのうえで今回上手く動作した環境は以下です。
- OS: Arch Linux (Linux 5.5.4-arch1-1)
- SDL_mixer: libSDL_mixer-1.2.so.0.12.0
- サウンドフォント: FluidR3_GM.sf2
- Simutrans: Simutrans 121.0
また、PulseAudio環境下での話です。
確認点
TiMidity++またはFluidSynth
まずそもそも、LinuxでMIDIを再生するためにこれらのうちの少なくとも一方を入れる必要があります。今回の場合どちらも入っている状態で片方ずつ確認しましたが、それぞれ正常に動作したのでどちらでも良いと思われます。また、単に導入するだけでも駄目で、
timidity -iA
やfluidsynth -a pulseaudio -m alsa_seq -o midi.autoconnect 1 [SOUND_FONT]
等でデーモン化させておく必要があります。個々のオプションは環境に合わせて調節してください。サウンドフォント
OSにより異なると思いますが、少なくともArchLinuxではTiMidity++やFluidSynthを導入してもサウンドフォントは入らないため、別に入れる必要があります。導入したサウンドフォントはデーモン化する際にTiMidity++の場合は
/etc/timidity++/timidity.conf
等、FluidSynthの場合は引数に指定してください。
SDL_SOUNDFONTS
SDL_mixerを使ったプログラムを実行する際に、上で設定したサウンドフォントのパスを
SDL_SOUNDFONTS=/path/to/soundfont.sf2 ./program
のように指定してください。試していないですが、恐らく.bashrc
等で設定しても大丈夫だと思います。ミュート
ここまでの作業で再生されるはずですが、もし再生されない場合は念の為ミュートになっていないか確認してください。今回の場合はここまでで上手く再生されました。
最後に
SDL_SOUNDFONTS
の情報が見つからずに嵌ってしまい、また見つかった際もFluidSynthをSDL_mixerと組み合わせる場合の情報として書いてあったので、探しづらかったです。なお、動作環境にも書きましたが、SDL2ではなくSDLでの話なのでSDL2では変わっている可能性があります。情報元
- ArchWiki (英語版): https://wiki.archlinux.org/index.php/FluidSynth#SDL_Mixer
- 投稿日:2020-02-19T18:17:02+09:00
fork()関数、execve()関数について
- 自分用メモ
- カーネルのプロセス生成について学んだことを残しておく
- C言語で実行
fork()関数について
- fork()関数を実行すると、親プロセスから子プロセスを生み出す
fork.c#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <err.h> static void child() { printf("Child. pid: %d.\n", getpid()); exit(EXIT_SUCCESS); } static void parent(pid_t pid_child) { printf("Parent. parent's pid: %d. child's pid: %d\n", getpid(), pid_child); exit(EXIT_SUCCESS); } int main() { pid_t pid; pid = fork(); if(pid == -1) err(EXIT_FAILURE, "fork() failed"); //子プロセスは返り値が0 if(pid == 0) child(); //親プロセスが返り値が子プロセスのプロセスID(> 0) else parent(pid); }
- 実行結果
Parent. parent's pid: 27701. child's pid: 27702 Child. pid: 27702.
親プロセスから子プロセスが生成され、それぞれ別のプロセスIDを持っていることが分かる
execv()関数について
- 以下の流れでカーネルはプロセスを実行する
- 実行ファイルを読み込む
- 現在のプロセスのメモリを新たなプロセスのデータで上書き
- 新しいプロセスを実行
execve.c#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <err.h> static void child() { char *args[] = {"/bin/echo", "こんにちは", NULL}; printf("child. child's pid: %d.\n", getpid()); execve("/bin/echo", args, NULL); err(EXIT_FAILURE, "execve() failed"); } static void parent(pid_t pid_child) { printf("parent. parent's pid: %d. child's pid: %d\n", getpid(), pid_child); exit(EXIT_SUCCESS); } int main(void) { pid_t pid; pid = fork(); if(pid == -1) err(EXIT_FAILURE, "fork() failed"); if(pid == 0) child(); else parent(pid); }
- 実行結果
parent. parent's pid: 28034. child's pid: 28035 child. child's pid: 28035. こんにちはfork()関数でプロセスが分岐したあと、子プロセスでexecve()関数によって、/bin/echoが実行されていることが分かる
- 投稿日:2020-02-19T16:53:52+09:00
新しいシェルスクリプトの教科書 ~リダイレクトとパイプ~
標準入出力
3つの標準入出力
名前 内容 標準入力 標準的な入力元。通常はキーボード。 標準出力 標準的な出力先。通常は端末ディスプレイ。 標準エラー出力 標準的なエラーの出力先。通常は端末ディスプレイ。 リダイレクト
- コマンドを実行する時に標準入出力の入力元、出力先を置き換えるシェルの機能
出力のリダイレクト
>
>
を使う例 標準出力をresult.txtにリダイレクト $ ps > result.txt $ cat result.txt PID TTY TIME CMD 12202 pts/0 00:00:00 bash 34100 pts/0 00:00:00 ps例 標準出力をhello.txtにリダイレクト $ echo hello > hello.txt $ cat hello.txt hello標準エラー出力のリダイレクト2>
例 標準エラー出力をファイルにリダイレクト $ ls /xxx 2> error.txt $ cat error.txt ls: /xxx にアクセスできません: そのようなファイルやディレクトリはありません入力のリダイレクト
<
<
を使う例 標準入力ファイルをword.txtとしてtrコマンドを実行 $ cat word.txt abcd ←word.txtの内容 my book $ tr b B < word.txt ←word.txtを入力元としてリダイレクト aBcd my Booknoclobberと出力のリダイレクト
- >によって出力をリダイレクトした場合、リダイレクト先のファイルが存在しない場合はファイルが新規作成される
- 一方、ファイルが存在する場合、ファイルの内容は失われて上書きされてしまう
- 既存ファイルの上書きを防ぐためにnoclobberをsetコマンドで設定する
- 無効にする場合は、
set +o noclobber
- noclobberを無視して出力リダイレクトで上書きは、
>|
を使う例 bashのnoclobberを設定する $ set -o noclobber 例 noclobberを設定した状態で既存ファイルに出力リダイレクト $ touch result.txt $ ps > resultx.txt -bash: result.txt: 存在するファイルを上書きできません 例 bashのnoclobberの設定を無効にする $ set +o noclobber 例 noclobberの設定を無視してリダイレクトで上書き $ ps >| result.txt $ cat resultxtxt PID TTY TIME CMD 11272 pts/0 00:00:00 bash 11384 pts/1 00:00:00 psリダイレクトと追記
- >による上書きではなく、元のファイルへ追記したい場合は、>>を使う
- 指定したファイルが存在しなければ、新規作成される
例 echo.txtへリダイレクトによる追記 $ echo line1 >> echo.txt $ echo line2 >> echo.txt $ echo line3 >> echo.txt $ echo cat echo.txt line1 line2 line3標準出力と標準エラー出力をまとめる
- リダイレクトの記号を1行に複数指定することで、標準出力と標準エラー出力をそれぞれ別のファイルに出力することができる
- 標準出力と標準エラー出力を同じファイルにリダイレクトしたい場合は、末尾に2>&1を書く
- 指定した同じファイルに、標準出力と標準エラー出力をリダイレクトしたい場合は、ファイル名の前に
>>
と指定する- エラーメッセージは、標準エラー出力にしておくとエラーの原因の調査がしやすい
例 標準出力と標準エラー出力を分けてリダイレクト $ ls /usr /xxx > result.txt >2 error.txt 例 標準出力と標準エラー出力を同じファイルにリダイレクト $ ls /usr /xxx > ls_result.txt 2>&1 ちなみに、 $ ls /usr /xxx > ls_result.txt 2>&1 $ ls /usr /xxx &> ls_result.txt は、同じ ちなみに、 $ ls /usr /xxx >> ls_result.txt 2>&1 $ ls /usr /xxx &>> ls_result.txt は、同じecho_stderr.sh例 エラーメッセージを標準エラー出力へ出力する # !/bin/bash echo message echo 'error message' 1>&2 ←この場合も、1は省略しても同じ意味$ ./echo_stderr.sh > result.txt error message/dev/null
- リダイレクトと組み合わせてよく使われる
- /dev/nullから読み込んでも、何もデータは返されない
- /dev/nullにデータを書き込んでもどこにも書き込まれることはなく、データは消える
- 出力リダイレクト先に/dev/nullを指定すると出力は捨てられ、何も出力されない
- 不要なメッセージを取り除くため
- コマンドからの出力に不要なメッセージが含まれてる場合や、通常のメッセージの中にエラーメッセージが埋もれてしまってエラー情報がわからない時などに使うと便利
例 /dev/nullの表示 $ cat /dev/null $ ←何も表示されない 例 lsコマンドの出力を/den/nullにリダイレクトして捨てる $ ls / > /dev/null 例 標準出力を/dev/nullにリダイレクトして標準エラー出力だけ表示 $ ls / /xxx > /dev/null ls: /xxx にアクセスできません: そのようなファイルやディレクトリはありません 例 標準エラー出力を/dev/nullにリダイレクトして標準出力だけ表示 $ ls / /xxx 2> /dev/null (ls / の結果だけ表示される)リダイレクトに関する注意点
スペースを入れてはいけないところにスペースを入れる
- リダイレクト記号の左に書くファイルディスクリプタ番号と>の間に、スペースを入れてはいけない
例 標準エラー出力を/dev/nullへリダイレクトしようとした失敗例 $ ls /usr /xxx 2 > /dev/nullリダイレクト先とリダイレクト元
- リダイレクト元とリダイレクト先を同一とすると、ファイルの中身が空になってしまう
リダイレクト記号のまとめ
記号 内容 < file 標準入力をfileに変更する > file 標準出力をfileに変更する >> file 標準出力をfileの末尾に追加する > file 2> file 標準エラー出力をfileに変更する 2>> file 標準エラー出力をfileの末尾に追加する 2> file >&m 標準出力をファイルディスクリプタm番のコピーにする n>&m ファイルディスクリプタn番をファイルディスクリプタm番のコピーにする &> file 標準出力を標準エラー出力の両方をfileに変更する &>> file 標準出力と標準エラー出力の両方をfileの末尾に追加する ヒアドキュメント
あるコマンドの入力をファイルとして指定するのではなく、直接シェルスクリプト内に記述する機能
- ヒアドキュメントの内容が標準入力にあるものとして、コマンドが実行される
- 標準入力としてコマンドに渡したい内容が複数行になる場合によく使う
- 最後の終了文字列は、1つの行に単独で書かなければならない
- ヒアドキュメントの内側では、パラメータ展開・コマンド置換・算術式展開が行われる
- ヒアドキュメントの中で$,`,\という展開に使用する記号をそのまま文字として扱う場合は、
\
でエスケープする- ヒアドキュメント全体で展開が行われないようにするには、<<の右に書く終了文字列をクォート
- 終了文字列のクウォートは、シングルクォーテーション・ダブルクォーテーション・バックスラッシュなど
- <<の代わりに<<-と書くと、ヒアドキュメントの内容と最後の終了文字列の行頭タブが無視される
ヒアドキュメントの構造
コマンド << 終了文字列 ヒアドキュメントの構造 終了文字列ls.sh例 catコマンドでヒアドキュメントの内容を表示 # !/bin/bash cat << END Usage: ls [OPTION]... [FILE]... List information about the FILEs Sort entries alphabetically END$ ./ls.sh Usage: ls [OPTION]... [FILE]... List information about the FILEs Sort entries alphabeticallyhere_param.sh例 ヒアドキュメントの中でパラメータ展開 # !/bin/bash script_name=ls cat << END Usage: $script_name [OPTION]... [FILE]... ←ヒアドキュメント内でパラメータ展開 List information about the FILEs Sort entries alphabetically ENDhere_noparam.sh例 ヒアドキュメントの中でパラメータ展開させない # !/bin/bash script_name=ls cat << END Usage: \$script_name [OPTION]... [FILE]... ←$の前に\でエスケープした List information about the FILEs Sort entries alphabetically ENDhere_noparam2.sh例 ヒアドキュメントの中でパラメータ展開させない # !/bin/bash script_name=ls cat << 'END' ←終了文字列をクウォート Usage: \$script_name [OPTION]... [FILE]... List information about the FILEs Sort entries alphabetically END$ ./here_noparam.sh Usage: $script_name [OPTION]... [FILE]... ←$script_nameが展開されていない List information about the FILEs Sort entries alphabeticallyヒアストリング
- ヒアドキュメントを1行にした書き方
- <<<の右側に書いた文字列が標準入力にあるものとしてコマンドが実行される
- <<<の右側に書いた要素に対しては、パラメータ展開・コマンド置換・算術式展開・ブレース展開・チルダ展開が行われる
ヒアストリングの構造
コマンド <<< 文字列例 ヒアストリングによる標準入力 $ str=abc $ tr b B <<< "$str" aBcパイプライン
複数のコマンドを一つに繋げる役割
- |の直後に改行を入れると、シェルはまだパイプラインの途中と解釈する
- 標準出力だけが次のコマンドに渡されて、標準エラー出力は渡されない
- 標準出力と標準エラー出力を合わせて次のコマンドに渡したい場合は、
2>&1
でリダイレクトするパイプラインの構造
コマンド1 | コマンド2例 lsコマンドの標準出力をlessコマンドで表示 $ ls | less ... ドキュメント ビデオ 音楽 画像 公開 (END)例 grepコマンドでpyを含む行を抽出、wcコマンドで行数をカウントする $ ls /usr/bin | grep 'py' | wc -l 6例 標準出力と標準エラー出力の両方がパイプラインで次のコマンドに渡される $ ls /usr /xxx 2>&1 | less ls: /xxx にアクセスできません: そのようなファイルやディレクトリはありません /usr: bin etc ... share src tmp (END)コマンドのグループ化
複数のコマンドを{}で囲んで1つのコマンドとしてまとめる機能
- 1行でまとめる際は、それぞれのコマンドの後ろに;をつける
group2.sh例 グループコマンドを使ってリダイレクト # !/bin/bash { date +%Y-%m-%d echo '/usr list' ls /usr } > result.txt例 グループコマンドを1行で記述 $ { date +%Y-%m-%d; echo '/usr list'; ls /usr; } > result.txt()で囲む書き方
- ()の内側のコマンドがサブシェル内で実行される
- サブシェルを利用すると、現在のシェルから子プロセスとして新しくシェルのプロセスが起動され、その中で()の内側に書いたコマンドが実行される
- ()の内側でカレントディレクトリを変更しても、親ディレクトリのシェルプロセスには影響を与えない
- ディレクトリを移動して処理を行った後、元のディレクトリに戻りたい場合に便利
- 親のシェルプロセスで設定していた変数はサブシェルで参照できるが、サブシェルで改めて値を代入しても、親のシェルには影響がない
subshell.shサブシェルを使ってリダイレクト # !/bin/bash ( date +%Y-%m-%d echo '/usr list' ls /usr ) > result.txtsubshell_cd.sh例 サブシェルの中でディレクトリを移動 # !/bin/bash cd /usr pwd ) echo '' echo 'sub shell' pwd echo 'cd in sub shell' echo /tmp pwd echo '' ) pwd$ ./subshell_cd.sh /usr sub shell /usr cd in sub shell /tmp ←サブシェルの中で移動した /usr ←元の/usrディレクトリに戻ってきているsubshell_param.sh例 サブシェルの中で変数代入 # !/bin/bash name=miyake echo "$name" ( echo 'sub shell' echo "$name" name=okita echo "$name" ) echo "$name"例 サブシェルの中で代入した値が影響していない $ ./subshell_param.sh miyake sub shell miyake okita miyake
- 投稿日:2020-02-19T11:20:05+09:00
scpコマンドでEC2ストレージからファイルを取り出す
備忘録的に記述
サーバー上でpwdを実行し、コピーしたいディレクトリまたはファイルを確認し、コピペ
$ pwd > /home/ubuntu/directory/to/folderローカルに戻り、scpをssh接続しながら実行する
$ pwd > ペーストしたいディレクトリの確認、今回はデスクトップにコピペ > /Users/username/desktop $ scp -i ~/.ssh/filename.pem -r ubuntu@x.xxx.xxx.x:/home/ubuntu/directory/to/folder /Users/username/desktopローカルのディレクトリと、ubuntu@以下の部分を逆にして書くと、ローカルのフォルダがサーバーにアップされてしまうため、注意!!!!
- 投稿日:2020-02-19T10:48:37+09:00
VMware vSphere ESXi 5.5 に CentOS 6.10 x86_64を導入する
VMware vSphere ESXi 5.5 に CentOS 6.10 x86_64を導入する
前提条件
- ネットワークアドレスは、192.168.222.0/24
- ゲートウェイは、192.168.222.254
- DNSサーバは、192.168.222.1
- VMware vSphere ESXi 5.5 の利用が可能であること
私のポリシー(とっても個人的な)
- CentOSを導入する際は、netinstallする。
- netinstallの際に利用するimageデータ取得先は、ftp.riken.jp様にする。理研様、バンザイ。
- LANGは、en_US.UTF-8にする。
- キーボードレイアウトは、jp106にする。
新規仮想マシンの作成
名前と場所の入力
仮想マシン名は、適宜任意で入力する。私は、
ZabbixServer4.0LTSonCentOS6.10x86_64
としました。
検証環境だと、一目で目的の仮想マシンを探したくなるので、ホスト名を入れるよりも目的や機能名・OS、6432bit辺りを書いたがいいですね。
ストレージの選択
画像ではローカルストレージしかありませんので、そちらを選択しています。
仮想マシンのバージョンを選択
仮想マシンのバージョンは、ESXi5.5で選択出来る最新バージョン(ver8)を選択します。
現行最新ESXi(2020/02/19時点)だと、6.7系になりサポート範疇となります。
参考: ESXi/ESX ホストおよび互換性のある仮想マシンのハードウェア バージョンのリスト (2007240)
ゲストOSの選択
Linux
でCentOS 4/5/6 (64ビット)
を選択します。
CPUの選択
適宜、利用している物理環境のリソース状況を鑑みて、設定します。
メモリの設定
メモリサイズを設定します。今回は小さなLinuxサーバを立てる目的だったので、2GB程にしました。512MBでもよかったかも。
ネットワークの選択
適宜、適切なネットワークを選択します。
アダプタに関しては、VMXNET3
を選択します。デフォルトで選択されているはずです。
SCSI コントローラの設定
ディスクの選択
ディスクの作成
HDDサイズを設定します。今回は、小さ目なサイズでしたのでシックで作成しています。
ケースに併せて、シンか、シックか、判断します。
詳細オプション
終了準備の完了
特にありません。これまでの設定サマリーが見れます。
下部にある完了前に仮想マシンの設定を編集
にチェックを付けておくと、次の画像の画面が見れます。仮想マシンのプロパティ
netinstall用にISOをマウントしておきます。
また、FDDは利用しませんので、デバイスから削除しておきます。(※この部分は、私の好みです。)
CentOSの導入(インストール)の開始
Install or upgrade an existing system
を選択します。
時限式で、放っておくとInstall or upgrade an existing system
が選択されます。
メディアテスト
ブータブルメディア(ISOメディア)を検知したら、テストをするかどうか問われます。
チェックしません。
言語の選択
冒頭で書いたとおり、英語圏を選択しますので
English
を選択します。
キーボードの選択
インストールメディアを指定
netinstallをするので、
URL
を指定します。
個人的にこれがなぜ好みなのかというと、設定したIPをそのまま継承してCentOSが起動するので、そのままssh接続に移行出来るからですね。
TCP/IPの設定
IPv6は無効にし、IPv4を手動設定します。
つまり、Enable IPv6 Support
をチェックアウトし、Enable IPv4 Support
をチェックインして、Manual Configuration
をチェックインします。
IP設定
URL設定
Imageのロード
Imageがロードされます。この際、ロードできない場合は、ネットワーク(IP,GW,DNS)を確認します。あとは、URLを再確認します。
GUIベースのインストールウィザード開始
インストール先を指定
ローカルディスクか、ネットワークにあるディスクか、選択します。
今回は、ローカルディスクですね。なので、Basic Storage Device
を選択します。
フォーマットの確認
指定したディスクをフォーマットするかどうか、問われます。
フォーマットします。
ホスト名の設定
タイムゾーンの設定
サーバは、日本になるので
Tokyo, Asia
を指定します。
rootパスワードの設定
rootパスワードを設定します。
パスワード強度が低い場合やパスワードポリシーに抵触する場合は、「それはどうなの?それにするの?本当に?」と聞かれますが、適宜判断してください。
パーティションの設定
自動的にパーティションを作成してくれますが、その適応範囲を決めます。
今回は、新規ディスクですので、Use All Space
を指定します。
パーティションのレイアウトを見たい場合は、Review and modify partitioning layout
をチェックしてから、進めます。
パーティションの書き込み確認
パーティション書き込んじゃうけどいいの?と問われているので、そのまま進めます。
つまり、Write changes to disk
を押します。
デフォルトインストールの選択
私は、GUIが嫌いなのでCLIベースになるようにします。
但し、Minimal
だと悲惨な目にあうので、Web Server
を選択します。
インストールの開始
完了
- 投稿日:2020-02-19T10:48:12+09:00
[Linux]自分のIPを変数に入れる方法
概要
シェルスクリプト内などで、自分のIPを変数に入れたい場合のTips
IPv4のみ対応です。環境
$ cat /etc/os-release NAME="Amazon Linux" VERSION="2" ID="amzn" ID_LIKE="centos rhel fedora" VERSION_ID="2" PRETTY_NAME="Amazon Linux 2" ANSI_COLOR="0;33" CPE_NAME="cpe:2.3:o:amazon:amazon_linux:2" HOME_URL="https://amazonlinux.com/"$ ip a|grep -e inet |grep -v inet6 inet 127.0.0.1/8 scope host lo inet 192.168.1.1/24 brd 192.168.1.255 scope global dynamic eth0 inet 192.168.2.1/24 brd 192.168.2.255 scope global dynamic eth1 $ hostname -i #eth0のみ表示 192.168.1.1 $ hostname -I #loを除く全てを表示 192.168.1.1 192.168.2.1コマンド
#一番シンプルな方法 $ MyIP=`hostname -i` $ echo $MyIP 192.168.1.1 #IPが複数ある場合 $ MyIPeth0=`hostname -I | cut -f1 -d' '` $ echo $MyIPeth0 192.168.1.1 $ MyIPeth1=`hostname -I | cut -f2 -d' '` $ echo $MyIPeth1 192.168.2.1コマンド2
hostnameコマンドは危険だから使うなって言われた場合はこちら
$ MyIPeth0=`ip -f inet -o addr show eth0|cut -d\ -f 7 | cut -d/ -f 1` $ echo $MyIPeth0 192.168.1.1 $ MyIPeth1=`ip -f inet -o addr show eth1|cut -d\ -f 7 | cut -d/ -f 1` $ echo $MyIPeth1 192.168.2.1補足
rootでhostnameコマンド実行時、'hostname -i'とするところを'hostname i'とすると
ホスト名がiになるので注意。$ hostname hogehoge $ hostname i hostname: you must be root to change the host name $ sudo su - # # hostname i # hostname i # MyIPeth0=`hostname I | cut -f1 -d' '` # hostname I以上