のぴぴのメモ

自分用のLinuxとかの技術メモ

RHEL7 initramfsの展開方法

今までのやり方では展開されない

普通、initramfsの中身を展開する場合は、gunzipあたりとcpioを使って以下のようにするはず。

$ cd /tmp
$ cp /boot/initramfs.img initramfs.gz
$ gunzip initramfs.gz
$ mkdir ramdisk
$ cd ramdisk
$ cpio -i -d -H newc --no-absolute-filenames < ../initramfs

ところが、最近のFedora/CentOS/RHELで上記をやると、これは「gzipじゃない!!」と怒られてしまう。

$gunzip initramfs.gz
gzip: initramfs.gz: not in gzip format

じゃあ、fileで確認するとcpioだというから直接cpioで展開すると、CPUのマイクロコードらしきものしか展開されない(´・ω・`)

$ mkdir ramdisk
$ cd ramdisk
$ file ../initramfs.gz
../initramfs.gz: ASCII cpio archive (SVR4 with no CRC)
$ cpio -i -d -H newc --no-absolute-filenames < ../initramfs.gz
26 blocks
$ find .
.
./kernel
./kernel/x86
./kernel/x86/microcode
./kernel/x86/microcode/GenuineIntel.bin
./early_cpio

[RHEL7/CentOS7/FC21]initramfsの展開方法

グーグルさんに聞いてみる

キーワードを変えていろいろ調べたら、出てきた。
forums.fedoraforum.org

手順

上記内容をちょっとだけ丁寧に解説。

バイナリ解析ツールのインストール

まずバイナリー解析用ツールを入れないといけないらしい。

yum -y install binwalk

なおRHELCentOSのレポジトリにはbinwalkはないので、ネットからrpmパッケージをDLしてrpmコマンドで個別にインストールするか、ソースを落としてコンパイルする。

initramfsの解析と展開
$ cp /boot/initramfs-3.10.0-229.el7.x86_64.img .
$ binwalk initramfs-3.10.0-229.el7.x86_64.img

DECIMAL         HEX             DESCRIPTION
-------------------------------------------------------------------------------------------------------
13312           0x3400          gzip compressed data, from Unix, last modified: Thu Apr 30 13:18:46 2015, max compression
3412955         0x3413DB        bzip2 compressed data

$ dd if=initramfs-3.10.0-229.el7.x86_64.img of=initramfs.gz bs=13312 skip=1
1350+1 records in
1350+1 records out
17982993 bytes (18 MB) copied, 0.0431475 s, 417 MB/s
$ gunzip initramfs.gz
$ mkdir ramdisk
$ cd ramdisk/
$ cpio -i -d -H newc --no-absolute-filenames < ../initramfs
80381 blocks
    • binwalk解析でgzipが始まる位置を確認(上記例では、13312bytesから開始)
    • ddコマンドでgzipの前のデータをスキップしてコピー("bs=13312 skip=1"がミソ)
    • それ以降は今までのやり方と同じ

なおgzipが始まる位置はその時々で変わる(おそらくmicrocodeのデータサイズが変わるから)ため、都度binwalk解析の確認が必要。

RHEL6 squashfsの作り方

squashfsとは

squashfsとは、圧縮された読み込み専用のファイルシステムでLiveCD(CD/DVDブートして利用するOS)で利用されている。詳しくはwikipediaを見るのが手っ取り早いかと。
wikipedia:SquashFS

squashfsファイルシステムの作り方

squashfs用ツールのインストール

squshfsなファイルシステムはmksquashfsコマンドを利用するが、CentOSなどではデフォルトでは入っていないため、個別にrpmパッケージ(squashfs-tools)インストールする必要がある。

# yum install squashfs-tools

squashfsファイルシステムの作成

mksquashfsコマンドで、squashfsファイルシステムを作成する。

$ mksquashfs ./SOURCE/ File.squashfs

squashfsファイルシステムのマウント

出来上がったsqushfsはmountコマンドのループバックオプションでマウントする。

#  mount -o loop File.squashfs /MountPoint/

fstabで自動マウント

fstabに登録するときは以下のように記載する。

/Directory/File.squashfs /MountPoint  squashfs loop 0 0

mountコマンドでマウントする。

# mount -a

追伸

昔、cloopで圧縮ファイルを作るということをやっていたけど(Fedora5/RHEL4 cloopでLinux圧縮ファイルシステムを作る - のぴぴのメモ、Upstreamにマージされていて各ディストリビューションにもデフォルトで入っているsquashfsのほうが使いやすいですよね。

Linux /proc/meminfoの統計情報を取得する。

/proc/meminfoの統計情報を取得する

 Linuxのメモリ挙動を詳しく分析するためには/proc/meminfoを見る必要がある。がしかし、sarコマンドのように時系列でmeminfo情報を取得してくれるコマンドはなく、自作のスクリプトなどで取得するしかない。  

そこで、スクリプトで/proc/meminfo情報を時系列で取得する方法を説明する。

  • 2013.3.4 awkコマンド修正(ヘッダ末尾のリターンコード忘れてた)

コマンドで取得する(テストなどの秒間隔での取得)

ざっくりいうと、meminfoをawkCSV形式に整形してログファイルに追加でリダイレクトするコマンドを、wlile分で無限ループさせているだけ。終了する場合は、CTRL+Cで強制終了させる。

LANG=C;( awk 'BEGIN{ printf "\"DATE\",\"TIME\""} {printf ",\"%s(%s)\"",$1,$3} END{printf "\n"}' /proc/meminfo > meminfo.csv ); while true;do ( awk 'BEGIN{ printf strftime("%Y/%m/%d, %H:%M:%S") } {printf ",%d",$2} END{printf "\n"}' /proc/meminfo >> meminfo.csv );sleep 1; done

これを動かすと、こんな結果が取得される。

"DATE","TIME","MemTotal:(kB)","MemFree:(kB)","Buffers:(kB)","Cached:(kB)","SwapCached:(kB)","Active:(kB)","Inactive:(kB)","Active(anon):(kB)","Inactive(anon):(kB)","Active(file):(kB)","Inactive(file):(kB)","Unevictable:(kB)","Mlocked:(kB)","SwapTotal:(kB)","SwapFree:(kB)","Dirty:(kB)","Writeback:(kB)","AnonPages:(kB)","Mapped:(kB)","Shmem:(kB)","Slab:(kB)","SReclaimable:(kB)","SUnreclaim:(kB)","KernelStack:(kB)","PageTables:(kB)","NFS_Unstable:(kB)","Bounce:(kB)","WritebackTmp:(kB)","CommitLimit:(kB)","Committed_AS:(kB)","VmallocTotal:(kB)","VmallocUsed:(kB)","VmallocChunk:(kB)","HardwareCorrupted:(kB)","AnonHugePages:(kB)","HugePages_Total:()","HugePages_Free:()","HugePages_Rsvd:()","HugePages_Surp:()","Hugepagesize:(kB)","DirectMap4k:(kB)","DirectMap2M:(kB)"
2013/03/03, 23:58:48,2054984,1161012,99012,605416,0,282204,439192,17128,8,265076,439184,0,0,4128760,4128760,8,0,16960,15536,172,132568,77788,54780,808,4056,0,0,0,5156252,140192,34359738367,274388,34359451196,0,0,0,0,0,0,2048,8192,2088960
2013/03/03, 23:58:49,2054984,1160996,99012,605412,0,282176,439192,17104,8,265072,439184,0,0,4128760,4128760,8,0,16976,15576,172,132580,77792,54788,816,4036,0,0,0,5156252,140192,34359738367,274388,34359451196,0,0,0,0,0,0,2048,8192,2088960
2013/03/03, 23:58:50,2054984,1160996,99020,605412,0,282176,439200,17104,8,265072,439192,0,0,4128760,4128760,20,0,16976,15576,172,132580,77792,54788,816,4036,0,0,0,5156252,140192,34359738367,274388,34359451196,0,0,0,0,0,0,2048,8192,2088960
2013/03/03, 23:58:51,2054984,1160996,99020,605412,0,282216,439200,17144,8,265072,439192,0,0,4128760,4128760,20,0,16972,15576,172,132580,77792,54788,816,4036,0,0,0,5156252,140192,34359738367,274388,34359451196,0,0,0,0,0,0,2048,8192,2088960

cronで取得する(日/週/月単位のトレンド取得)

長期のトレンドを見たい場合は、cronで分以上の間隔で取得するのが良い。

取得用のシェル

  • /usr/lib64/sa/meminfo.sh
#!/bin/bash
LANG=C
PATH=/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/sbin:
export LANG PATH
umask 022

LOGFILE_BASE="/var/log/sa/meminfo"
LOGFILE="${LOGFILE_BASE}.`date +%Y%m%d`"

# Write header, if LOGFILE does not exist.
if [ ! -f ${LOGFILE} ]; then
    awk '
        BEGIN{
            printf "\"DATE\",\"TIME\""
        }
        {
            printf ",\"%s(%s)\"",$1,$3
        }
        END{
            printf "\n"
        }' /proc/meminfo > ${LOGFILE}

    fi

# Write data
awk '
        BEGIN{
            printf strftime("%Y/%M/%d, %H:%M:%S")
        }
        {
            printf ",%d",$2
        }
        END{
            printf "\n"
        }' /proc/meminfo >> ${LOGFILE}

exit 0

シェルをキックするcron設定

1分間隔で取得する。

  • /etc/cron.d/meminfo
# Get meminfo.
* * * * * root /usr/lib64/sa/meminfo  > /dev/null 2>&1

ログのローテーションは。。。別に考えよう(^^;

シェル用のメッセージロギングツール

シェルのメッセージを一元的に出力する自作のツールです。
職場で使われているロギングツールがシンプルで使いやすいので、同じようなものを自作したものです。

ツールはgithubで公開しています。

configureの作り方(autotoolsの使い方)

はじめに

オープンソースでよくある、configure → make → make installというセットアップの流れ。これはGNUのautotoolsというツール&フレームワーク群により実現されている。
本記事ではそのautotoolsによりconfigureを作成する手順を説明する。

作成の流れ

  1. 必要ファイルの作成(automake時に必要)
  2. Makefile.am作成(後の作成/修正も可)
  3. autoscanコマンドの実行
  4. configure.acファイルの作成&編集
  5. aclocal実行
  6. autoheader実行
  7. automake実行
  8. autoconf実行

ユーザが編集するファイルは、configure.acとMakefile.am。これらファイルを修正した時は、aclocalコマンド以降を都度実行してファイルを再生性する。なので、aclocalコマンド以降をシェル化して一気に実行するようにしておくと便利である。

f:id:nopipi:20130109013829p:plain

手順詳細

必要ファイルの作成

automakeコマンド実行時に求められるファイルを作成する。とりあえずファイルがあればよいので、空ファイルを作成する。(内容は必要に応じ別途修正する)

$ touch INSTALL NEWS README LICENSE AUTHORS ChangeLog

Makefile.am作成(後の作成/修正も可)

次のautoscanコマンドで、AC_CONFIG_FILESを自動生成するため、とりあえずMakefile.amを作成する。とりあえずこの段階では空ファイルでもかまわない。(automake実行時に中身があればOK)

 -> Makefile.amのつくり方(別途作成予定)

autoscanコマンドの実行

configure.scanを作成し、それをRenameしてconfigure.acを作成する。

$ autoscan
$ mv configure.scan configure.ac

configure.acファイルの作成&編集

configure.acを修正する。詳細は別途作成

#                                               -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.

sAC_PREREQ([2.63])
AC_INIT([FULL-PACKAGE-NAME], [VERSION], [BUG-REPORT-ADDRESS]) <== パッケージ&バージョン&連絡先を記入
AC_CONFIG_SRCDIR([src/uplog_util.c])
AC_CONFIG_HEADERS([config.h])

# Checks for programs.
AC_PROG_CC

# Checks for libraries.

# Checks for header files.
AC_CHECK_HEADERS([fcntl.h limits.h locale.h stdlib.h string.h sys/socket.h syslog.h unistd.h])

# Checks for typedefs, structures, and compiler characteristics.
AC_TYPE_PID_T

# Checks for library functions.
AC_FUNC_FORK
AC_FUNC_MALLOC
AC_FUNC_STRNLEN
AC_CHECK_FUNCS([dup2 getcwd gethostname gettimeofday memset realpath select setlocale socket strerror])

AC_CONFIG_FILES([Makefile
                 conf/Makefile
                 src/Makefile]) <==Makefile.amを増やしたらここに追加
AC_OUTPUT

aclocal実行以降

以後はシェル化し、Makefile.amやconfigure.ac修正時に都度実行すると楽。

echo aclocal
aclocal
echo autoheader
autoheader
echo automake
automake --add-missing --copy
echo autoconf
autoconf

ESXi TeraTermマクロ自動化

本当はvSphere CLIをインストールして利用すればよいのだが、VMのPowerOnとハイパーバイザのシャットダウンだけで十分だったので、TeraTermのマクロで自動化。

前提

ESXiは、SSH 鍵認証でログインできること。
SSH設定詳細はこちら→ESXi5 SSH 有効化&鍵認証(TeraTermの自動ログインマクロ付) - のぴぴのメモ

ESXi Hypervisorシャットダウン

コマンド"vim-cmd hostsvc/standby_mode_enter'"で停止。

; ==========================================================
; Setting (IP, UserName, KEY file)
HOSTADDR = 'xxx.xxx.xxx.xx'       ;ESXiのIPアドレス
USERNAME = 'root'
KEY_FILE = 'xxxxxxxxxxxxxxxxxxxx' ;ログインに使用する秘密鍵
CNF_FILE = 'xxxxxxxxxxxxxxxxxxxx' ;TeraTermの設定ファイル
; ==========================================================


;; Generate login command
COMMAND = HOSTADDR
strconcat COMMAND ':22 /ssh /2 /auth=publickey /user='
strconcat COMMAND USERNAME
strconcat COMMAND ' /keyfile="'
strconcat COMMAND KEY_FILE
strconcat COMMAND '" /F="'
strconcat COMMAND CNF_FILE
strconcat COMMAND '"'

;; Login
connect COMMAND
wait '~ #'

;; shutdown hypervisor
sendln 'vim-cmd hostsvc/standby_mode_enter'
sendln 'exit'

end

VM PowerON

  1. "vim-cmd vmsvc/getallvms"コマンドで、VM一覧を取得
  2. 取得したVM一覧よりリストを生成、起動したいVMの番号をインプットボックスより入力
  3. "vim-cmd vmsvc/power.getstate vim"で、各VMのPower状態を取得
  4. "vim-cmd vmsvc/power.on VMID"コマンドでVMを起動
; ==========================================================
; Setting (IP, UserName, KEY file)
HOSTADDR = 'xxx.xxx.xxx.xx'       ;ESXiのIPアドレス
USERNAME = 'root'
KEY_FILE = 'xxxxxxxxxxxxxxxxxxxx' ;ログインに使用する秘密鍵
CNF_FILE = 'xxxxxxxxxxxxxxxxxxxx' ;TeraTermの設定ファイル
; ==========================================================
;; Initialize
NUM=256
strdim VM_NO   NUM
strdim VM_NAME NUM
strdim VM_STAT NUM

timeout=1


;; Generate login command
COMMAND = HOSTADDR
strconcat COMMAND ':22 /ssh /2 /auth=publickey /user='
strconcat COMMAND USERNAME
strconcat COMMAND ' /keyfile="'
strconcat COMMAND KEY_FILE
strconcat COMMAND '" /F="'
strconcat COMMAND CNF_FILE
strconcat COMMAND '"'

;; Login
connect COMMAND
wait '~ #'


;;PowerOn VM
count = 0
result = 1
sendln 'vim-cmd vmsvc/getallvms'
waitln 'Vmid'
while result
    ; receive VM list
    recvln
    if result = 0 then
        goto getall_fail
    endif
    strmatch inputstr '~ #'
    if result <> 0 then
        goto getall_fail
    endif
    
    ; Split
    strreplace inputstr 1 '\s+' ' '
    strsplit inputstr " "
    VM_NO[count]   = groupmatchstr1
    VM_NAME[count] = groupmatchstr2
    
    count = count + 1
    goto getall_end
    :getall_fail
    result = 0
    
    :getall_end
endwhile

; Get VM power status
for i 0 count-1
    ; get VM stat
    MSG = 'vim-cmd vmsvc/power.getstate '
    strconcat MSG VM_NO[i]
    sendln MSG
    waitln 'Retrieved'
    
    recvln
    strsplit inputstr " "
    VM_STAT[i] = groupmatchstr2
next


;;Select VM
MSG="Select VM Number\n-----------------------------\n"
for i 0 count-1
    int2str stri  i
    strconcat MSG stri
    strconcat MSG '.\t'
    strconcat MSG VM_STAT[i]
    strconcat MSG '\t'
    strconcat MSG VM_NAME[i]
    strconcat MSG '\n'
next
strconcat MSG '\n\n-1\tCancel\n-----------------------------'

:select_RETRY
inputbox MSG 'select VM' 1
str2int vmnum inputstr
if result = 0 then
    goto select_RETRY
endif
if vmnum < 0 then
    goto EXIT_MACRO
endif


; PowerON VM
COMMAND= 'vim-cmd vmsvc/power.on '
strconcat COMMAND VM_NO[vmnum]
sendln COMMAND
wait '~ #'

;;exit
:EXIT_MACRO

sendln 'exit'
end

VM PowerOFF

  1. "vim-cmd vmsvc/getallvms"コマンドで、VM一覧を取得
  2. 取得したVM一覧よりリストを生成、起動したいVMの番号をインプットボックスより入力
  3. "vim-cmd vmsvc/power.getstate vim"で、各VMのPower状態を取得
  4. "vim-cmd vmsvc/power.off VMID"コマンドでVMを停止
; ==========================================================
; Setting (IP, UserName, KEY file)
HOSTADDR = 'xxx.xxx.xxx.xx'       ;ESXiのIPアドレス
USERNAME = 'root'
KEY_FILE = 'xxxxxxxxxxxxxxxxxxxx' ;ログインに使用する秘密鍵
CNF_FILE = 'xxxxxxxxxxxxxxxxxxxx' ;TeraTermの設定ファイル
; ===========================================================
;; Initialize
NUM=256
strdim VM_NO   NUM
strdim VM_NAME NUM
strdim VM_STAT NUM

timeout=1


;; Generate login command
COMMAND = HOSTADDR
strconcat COMMAND ':22 /ssh /2 /auth=publickey /user='
strconcat COMMAND USERNAME
strconcat COMMAND ' /keyfile="'
strconcat COMMAND KEY_FILE
strconcat COMMAND '" /F="'
strconcat COMMAND CNF_FILE
strconcat COMMAND '"'

;; Login
connect COMMAND
wait '~ #'


;;PowerOn VM
count = 0
result = 1
sendln 'vim-cmd vmsvc/getallvms'
waitln 'Vmid'
while result
    ; receive VM list
    recvln
    if result = 0 then
        goto getall_fail
    endif
    strmatch inputstr '~ #'
    if result <> 0 then
        goto getall_fail
    endif
    
    ; Split
    strreplace inputstr 1 '\s+' ' '
    strsplit inputstr " "
    VM_NO[count]   = groupmatchstr1
    VM_NAME[count] = groupmatchstr2
    
    count = count + 1
    goto getall_end
    :getall_fail
    result = 0
    
    :getall_end
endwhile

; Get VM power status
for i 0 count-1
    ; get VM stat
    MSG = 'vim-cmd vmsvc/power.getstate '
    strconcat MSG VM_NO[i]
    sendln MSG
    waitln 'Retrieved'
    
    recvln
    strsplit inputstr " "
    VM_STAT[i] = groupmatchstr2
next

;;Select VM
MSG="Select VM Number\n-----------------------------\n"
for i 0 count-1
    int2str stri  i
    strconcat MSG stri
    strconcat MSG '.\t'
    strconcat MSG VM_STAT[i]
    strconcat MSG '\t'
    strconcat MSG VM_NAME[i]
    strconcat MSG '\n'
next
strconcat MSG '\n\n-1\tCancel\n-----------------------------'

:select_RETRY
inputbox MSG 'select VM' 1
str2int vmnum inputstr
if result = 0 then
    goto select_RETRY
endif
if vmnum < 0 then
    goto EXIT_MACRO
endif


; PowerON VM
COMMAND= 'vim-cmd vmsvc/power.off '
strconcat COMMAND VM_NO[vmnum]
sendln COMMAND
wait '~ #'

;;exit
:EXIT_MACRO

sendln 'exit'
end

ESXi5 SSH 有効化&鍵認証(TeraTermの自動ログインマクロ付)

VMware ESXi5をSSHでリモートで操作したい場合の設定手順を記録する。

1.ESXのSSHサービスを有効にする

SSHのサービス有効化は、コンソール画面とvSphereClientの2通りのがあるらしい。
ここではコンソール画面からの変更を記載する。

  • "F2"キーを押し、コンソールにログインする
  • "Trubleshooting Options"を選択する
  • "Enable SSH"を選択し「リターン」キーを押す

これでSSHが有効化される。

ちなみにコンソール画面から直接操作するには、ひとつ上の項目"Enable ESXi shell"を有効化する。ついでに、コンソールシェル(ESXi shell)も有効化する。(必須ではない)

  • "Trubleshooting Options" -> "Enable ESXi shell"でシェルを有効かする。
  • ESXi shellを有効化したあと"ALT+F1"キーを押すとシェル画面に切り替わる
  • 元の画面(System Coustomization)に戻るには、"ALT+F2"キーを押す

2.TeraTermからパスワード認証ログインを確認

TeraTermからSSHのパスワード認証でログイン可能なことを確認する。ここで中が必要なのは、ESXi5では「チャレンジレスポンス認証」を利用しているらしく、TeraTermSSH認証で「チャレンジレスポンス認証を使う(キーボードインタラクティブ)」を選択する必要があることである。(下図参照)
f:id:nopipi:20121117215649p:plain

3.鍵の設定

SSHでとりあえずログインできることを確認したら、ESXiにログインに利用する公開鍵を登録する。

  • TeraTermで鍵を作成する(詳細はググルこと)
    • 「設定(S)」->「SSH鍵生成(N)」を選択
    • 鍵の種類を「RSA」にして「生成(G)」ボタンで鍵を生成
    • 公開鍵&秘密鍵を保存する
  • ESXiにパスワード認証でログインする

~ # cd /etc/ssh/keys-root
/etc/ssh/keys-root #
  • TeraTermで作成した公開鍵をエディタで開きファイル内の文字列をauthorized_keysに貼り付ける
/etc/ssh/keys-root # cat > authorized_keys
<<公開鍵のテキストカット&ペーストする>>
"CTL+D"を押すと入力終了する
/etc/ssh/keys-root #
  • TeraTermから鍵認証でログインできることを確認する
  • TeraTermからESXiにSSH接続
  • SSH認証画面で「RSA/DSA鍵を使う」を選択し、秘密鍵のファイルを指定

f:id:nopipi:20121117220601p:plain

4.TeraTerm鍵認証による自動ログインマクロ

参考までに、SSH鍵認証の場合の自動ログインマクロを公開。

; ==========================================================
; Setting (IP, UserName, KEY file)
HOSTADDR = 'xxx.xxx.xxx.xx'       ;ESXiのIPアドレス
USERNAME = 'root'
KEY_FILE = 'xxxxxxxxxxxxxxxxxxxx' ;ログインに使用する秘密鍵
CNF_FILE = 'xxxxxxxxxxxxxxxxxxxx' ;TeraTermの設定ファイル
; ==========================================================


;; Generate login command
COMMAND = HOSTADDR
strconcat COMMAND ':22 /ssh /2 /auth=publickey /user='
strconcat COMMAND USERNAME
strconcat COMMAND ' /keyfile="'
strconcat COMMAND KEY_FILE
strconcat COMMAND '" /F="'
strconcat COMMAND CNF_FILE
strconcat COMMAND '"'

;; Login
connect COMMAND
wait '~ #'

end

ダイレクトIOの実装

ファイルシステム上のファイルにキャッシュを解さずread/writeするためには、DirectIOで操作する必要がある。DirectIOを操作するには、

  1. ファイルをオープンする時に、O_DIRECTオプションを付与する
  2. read/writeで操作する際のバッファは、512KB(Linux kernel2.6以降)でアライメントする(したがって、バッファ意はposix_memalignで確保する。mallocや配列で確保するとread()/write()時に、EINVALエラーとなる)
  3. コンパイル時に"_GNU_SOURCE"オプションを付与する。

サンプルコードは以下の通り。

#include <unistd.h>
#include <fcntl.h>


#define BLOCKSIZE 512


int main(void)
{
        int  fdi, fdo;
        int  size = 100 * 1024 * 1024; /* 100MB */
        char *buf;

        /* allocate buffer memory */
        posix_memalign( (void **)&buf, BLOCKSIZE, size);

        /* open file */
        fdi = open("file_in", O_RDONLY                |O_DIRECT );
        fdo = open("file_out",O_WRONLY|O_CREAT|O_TRUNC|O_DIRECT,
                                S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP );

        /* copy "fdi" file to "fdo" file. */
        read(fdi, buf, size);
        write(fdo,buf, size);

}

コンパイルコマンド

$ gcc -D_GNU_SOURCE -o directio directio.c

テスト

$ dd if=/dev/zero of=file_in bs=1024 count=102400
102400+0 records in
102400+0 records out
104857600 bytes (105 MB) copied, 1.31783 s, 79.6 MB/s
$ ./directio

straceコマンドでシステムとレースしてみても

$ strace ./directio
   <中略>
open("file_in", O_RDONLY|O_DIRECT)      = 3
open("file_out", O_WRONLY|O_CREAT|O_TRUNC|O_DIRECT, 0660) = 4
read(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 104857600) = 104857600
write(4, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 104857600) = 104857600
exit_group(104857600)                   = ?
$

デーモンは2度forkするのが作法らしい

デーモンプロセス起動時の作法

デーモンを起動する場合、2度forkするのが作法らしく、これは「セッションリーダにならないようにすることで端末の制御を誤ってしないように」ということらしい。

詳しくはこちらを参照

そうなのかぁと思いながら、見よう見まねでdaemonの関数を作ってみました。

サンプルプログラム

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

static pid_t do_daemon(int close_interface)
{

        pid_t ret;
        int   fd;

        /*create child process and terminate parent process*/
        ret = fork();
        if( ret < 0 ){
                /* fork error */
                perror("Cannot fork");
                return(-1);
        }else if( ret != 0 ){
                /* parent process */
                return(ret);

        }
        /* child process */

        /* change chiled process to session gruop reader */
        ret = setsid();
        if( ret < 0 ){
                perror("Cannot create a session");
                return(-1);
        }

        /* create 2nd child process and terminate parent process again
         * to never connect terminal
         */
        ret = fork();
        if( ret < 0 ){
                /* fork error */
                perror("Cannot fork");
                return(-1);
        }else if( ret != 0 ){
                /* parent process */
                return(ret);
        }

        /* change the current directory to root directory */
        (void) chdir("/");

        /* close stdin/stdout/stderr */
        fd = open("/dev/null", O_RDWR);
        if( fd < 0 ){
                perror("Cannot open NULL device");
                return(-1);
        }

        /* close the STDIN file descriptor */
        if ((fd != STDIN_FILENO) && (dup2(fd, STDIN_FILENO) < 0)){
                perror("Cannot close the STDIN");
                (void)close(fd);
                return(-1);
        }

        /* close the STDOUT and STDERR file descriptor, if option is TRUE */
        if( close_interface ){
                /* close the STDOUT file descriptor */
                if ((fd != STDOUT_FILENO) && (dup2(fd, STDOUT_FILENO) < 0)) {
                        perror("Cannot close the STDOUT");
                        (void)close(fd);
                        return(-1);
                }

                /* close the STDERR file descriptor */
                if ((fd != STDERR_FILENO) && (dup2(fd, STDERR_FILENO) < 0)) {
                        perror("Cannot close the STDERR");
                        (void)close(fd);
                        return(-1);
                }
        }

        if( fd > STDERR_FILENO ){
                (void)close(fd);
        }

        /* daemon complete */
        return(0);

}

でもudevとかsyslogdとかのソースを見ると、案外fork一度しかやってなかったりするんだけどね。

はてな記法

Hatenaで記事を書く上で、やっぱり「はてな記方法」を知らないと綺麗な記事はかけないですね。
ということで、「はてな記法」のリファレンス

【RHEL6】yumでRHEL6のDVDからrpmパッケージをインストールする方法

1.RHELメディアのマウント

今回の例では、/mntにDVDをマウントする。

mount -t iso9660 -o ro /dev/cdrom /mnt

2.yumレポジトリの設定

/etc/yum.repos.d/に、xxx.repoというファイルを作る。(xxxは任意)
/etc/yum.repos.d/mnt_rhel60.repo

[rhel60_dvd]                ※1
name=RHEL6.0(x86_64) DVD    ※2
baseurl=file:///mnt/Server/
enabled=1           ※3
gpgcheck=1           ※4
gpgkey=file:///mnt/RPM-GPG-KEY-redhat-release ※5

※1 : レポジトリ名(わかりやすい名前をつける)
※2 : ラベル名(わかりやすい名前をつける)
※3 : 常に参照する場合は1。0の場合、yumオプションで"--enablerepo="を指定する。
※4 : GPG認証チェックを有効化する
※5 : GPG-KEYのファイルパスを指定

3.RHN接続の無効化(任意)

必須ではないが、RHNの接続エラーが毎回出るので抑止する。(enabled=0とする)

/etc/yum/pluginconf.d/rhnplugin.conf
[main]
#enabled = 1
enabled = 0
gpgcheck = 1

4.確認

yumコマンドで、DVDのパッケージが認識できるか確認。

# yum list
Loaded plugins: refresh-packagekit
Installed Packages
   <中略>
cdparanoia.x86_64     10.2-5.1.el6      rhel60_dvd
cdparanoia-libs.i686  10.2-5.1.el6      rhel60_dvd
cdrdao.x86_64         1.2.3-4.el6       rhel60_dvd
celt051.x86_64        0.5.1.3-0.el6     rhel60_dvd
check.i686            0.9.8-1.1.el6     rhel60_dvd
    <中略>

※指定したレポジトリ名のパッケージが表示されることを確認。

【RHEL6】ipv6無効化

ipv6の無効化方法について説明。

1.設定

/etc/modprobe.d/にdisable_ipv6.confというファイル設定ファイルを作成する。
(ファイル名は任意の名前で可)
/etc/modprobe.d/disable_ipv6.conf

options ipv6 disable=1

2.IPv6向けiptableサービスの無効化

(1)ip6tablesサービスの停止

# service ip6tables stop
ip6tables: モジュールを取り外し中:              [  OK  ]

(2)ip6tablesサービスの自動起動抑止

# chkconfig ip6tables off
# chkconfig --list|grep ip6tables

3.ネットワークの再起動

ネットワークを再起動する。

# service network restart

(面倒であればOSの再起動でもいいんでは)

4.確認

(1)dmesg設定

dmesgに下記メッセージがあることを確認。

IPv6: Loaded, but administratively disabled, reboot required to enable

※(意訳)モジュールをロードしたけど、管理上機能が無効化されてるよ

(b)ifconfigでipv6アドレスがないことを確認

 ifconfigにてIPv6の情報が表示されていないことを確認する。

-IPv6が有効な場合
$ ifconfig
eth0 Link encap:Ethernet HWaddr XX:XX:XX:XX:XX:XX
inet addr:192.168.1.10 Bcast:192.168.0.255 Mask:255.255.255.0
inet6 addr: fe80::240:63ff:feea:7bed/64 Scope:Link
<以下略>
-IPv6が無効な場合
$ ifconfig
eth0 Link encap:Ethernet HWaddr XX:XX:XX:XX:XX:XX
inet addr:192.168.1.10 Bcast:192.168.0.255 Mask:255.255.255.0
<以下略>

以上

【参考】

 一般的な方法として、modprobe.confに「alias net-pf-10 off」と設定する方法があちらこちらで紹介されている。しかいこの設定は、「off」という存在しないモジュールをロードさせようとすることで(当然失敗する)、結果としてipv6のモジュールをロードさせないようにしている。
 おそらく、disableオプションが実装される前の苦肉の策の設定ではないだろうか。

Fedora/RHEL シリアルコンソールの設定

1.はじめに

 シリアル接続した端末(PC)からコンソール操作が行えるように設定を行う。(PCからはTeraTermのコンソール接続を利用する。)

2.設定

(1)GRUBの設定

(i)シリアルコンソールからのGRUB操作を可能にする。

 /boot/grub/grub.confに以下の設定を行う。

/boot/grub/grub.confの編集内容

①GRUBをtext表示にするため、splashimageをコメントアウトする。
# splashimage=(hd0,0)/grub/splash.xpm.gz 

②シリアルポートの通信設定を行う
serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1

③グラフィックとシリアルの両方が使用できるよう設定
terminal --timeout=3 serial console
  ※3秒以内にキー入力があった場合はserial接続、
    無い場合は、グラフィック接続。
    必ずserial接続にする場合は、"terminal serial"
(ii)GRUB設定を反映する。

   実際は、次のkernelも纏めて設定を行た後の方が効率的。

# grub-install /dev/hda

(2)カーネルの設定

カーネルのブートオプションにシリアルコンソールの設定を追加する。
設定は、/boot/grub/grub.confに設定を行う。
/boot/grub/grub.confの編集内容

カーネルオプションに、"console=tty0 console=ttyS0,115200n8r"を追加する。
kernel /vmlinuz-2.6.19-1.2288.2.1.fc5 ro root=LABEL=/ console=tty0 console=ttyS0,115200n8r
(ii)GRUB設定を反映する。
# grub-install /dev/hda

(3)シリアルコンソールからのログイン設定

シリアルコンソールからログインが可能になるよう設定を行う。

(i)端末設定

inittabにシリアルコンソールの設定を追加する。
/etc/inittabに以下の設定を追加

co:2345:respawn:/sbin/agetty -h 115200 ttyS0 vt100

  下記コマンドで設定を反映する。

# init q
(ii)シリアルコンソールからのrootログイン許可

デフォルトではrootユーザのログインは不許可であるため、/etc/securettyにttyS0(シリアルのデバイス)設定を追加する。

console
vc/1
 <中略>
tty10
tty11
ttyS0 <- 追加

irqbalanceサービスについて

1.irqbalance概要

 FedoraRHEL,Suseなど多くのLinuxディストリビューションに標準実装されているデーモンで、マルチCPU環境において、IRQ割込み処理を複数のCPU間で負荷分散させることを目的としています。

2.詳細

 Linux Kernelはデフォルトの状態では、CPU0のみでIRQ割込み(ハードウエアからの割込み要求)の処理を行います。しかしそれではIRQ割り込みが頻繁に発生する場合CPU0に負荷が偏りパフォーマンスが劣化する可能性があります。そこでマルチCPU環境でにおいてirqbalanceを導入することで、2nd CPU以降も割り込み処理を行えるようになります。

 irqbalanceは、10秒毎に各CPUのIRQ割込み処理負荷状態状態に応じ、各CPUへのIRQ割込み処理の再配置をおこないます。CPUのIRQ割込み処理負荷状態は"/proc/interrupts"から算出しています。またIRQの再配置は、"/proc/irq/[IRQ番号]/smp_affinity"のCPUマスク設定を変更することで実現しています。

List.1 /proc/interruptsの例

# cat /proc/interrupts
           CPU0       CPU1
  1:         76          0  Phys-irq-level     i8042
  6:          5          0  Phys-irq-level     floppy
  7:          0          0  Phys-irq-level     parport0
  8:          0          0  Phys-irq-level     rtc
  9:          0          0  Phys-irq-level     acpi
 12:        147       2867  Phys-irq-level     i8042
 14:          0          0  Phys-irq-level     libata
 15:       5427       6975  Phys-irq-level     libata
 17:       9064       1343  Phys-irq-level     ioc0
 18:    1525405       6344  Phys-irq-level     peth0
256:     377093          0  Dynamic-irq-level     timer0
257:       6835          0  Dynamic-irq-level     resched0
258:         35          0  Dynamic-irq-level     callfunc0
259:          0       6868  Dynamic-irq-level     resched1
260:          0         89  Dynamic-irq-level     callfunc1
261:          0     153764  Dynamic-irq-level     timer1
262:        144          0  Dynamic-irq-level     xenbus
263:          0          0  Dynamic-irq-level     console
NMI:          0          0
LOC:          0          0
ERR:          0

List.2 /proc/irq/[IRQ番号]/smp_affinityの例

# cat /proc/irq/17/smp_affinity
00000000,00000001

 なおirqbalanceの実行周期はirqbalanceのソースコードにハードコーティングされており、設定で変更することはできません。

 各CPUの割込み状態の確認は、/proc/interrupts にて確認することが可能です。
 1CPU(1core)のみの環境の場合、irqbalanceを利用する必要はありません。(起動しようとしてもエラーとなります。)


3.サービス起動可否の考え方

irqbalanceに対するサービス可否の考え方を以下に示します。

  ・マルチCPU(マルチcore)環境の場合、irqbalanceを有効にする。
  ・1CPU(1core)環境の場合はirqbalanceを無効化する。

Fedora/RHEL 169.254.0.0ルーティング情報の削除

ルーティング情報(routeなどで出力)に設定していない、169.254.0.0という情報が表示される。これは、DHCPでIP情報が取得できなかった場合に使用するAPIPAの情報らしいが、必要ないので無効化する。

1.設定

 /etc/sysconfig/networkに下記設定を追加する。ネットワークをリスタートまたはOSのリブートし設定を反映する。

NOZEROCONF=yes

2.確認

 routeコマンドまたは"netstat -r"コマンドで169.254.0.0のルーティング情報が表示されないことを確認する。