のぴぴのメモ

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

ダイレクト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一度しかやってなかったりするんだけどね。

【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のルーティング情報が表示されないことを確認する。

Fedora5/RHEL4 cloopでLinux圧縮ファイルシステムを作る

1.はじめに

 cloopは、Debian系の1CD Linuxとして有名なKNOPPIXのルートファイルシステムとして利用されていることで有名であるが、redhatカーネルでの動作事例はインターネット上にあまりなく、あっても多くが古い情報である。
 今回、FedoraCore5とRHEL4にcloopしたので、その導入手順を纏める。(手順はFedoraCore5ベースで記述するが、RHEL4でもほとんど手順は同じである。)

※cloopは、読み込み専用のファイルシステム

2.手順(cloopデバイスの組み込み)

(1)前提のパッケージ

(i) カーネルソースコード

  適用するカーネルソースコードを用意する。
   ※ディストリビューションによっては、
    カーネルのヘッダファイルが標準でインストール
    されている。(/usr/src/kenelの下など)その場合は、
    別途ソースコードをインストールする必要はない。(2007.7.21追記)

(ii) zlib-devel(zlib開発用ライブラリ)

  zlib-develがインストールされてなければインストールする。
    ・zlib-develの確認

# rpm -q zlib-devel
    ・zlib-develがなければインストール
# yum install zlib-devel

(2)cloopソースコードの入手

 cloopのREADMEには、KNOPPIXの公式サイトの記載があるが、最新のソースコードは見当たらない。探したところ、Debianのunstableパッケージから入手が可能である。
 最新版は、"cloop-2.05~20060829"(2007.3.2現在)

(i)Debian公式サイトへ行く
(ii)下記順番でクリックする

  ・「Debian パッケージ」
  ・「不安定版 (unstable) ディストリビューションのパッケージを見る」
  ・「Miscellaneous」
  ・「cloop-src」パッケージを選択

(iii)記事執筆時点の最新版(と思われる)「cloop_2.05~20060829-1.tar.gz」をダウンロード

(3)cloopのソースの展開とコンパイル&インストール

(i)ソースコードの展開

  任意の場所(手順では/tmp)にtarのデータを展開する。

$ cd /tmp
$ tar -zxvf cloop_2.05~20060829-1.tar.gz 
$ cd cloop-2.05~20060829/
(ii)【RHEL4のみ】RHEL用にソースコードを修正

  RHELの場合、そのままではコンパイルが通らないため一部のソースを修正する。

$ vi compressed_loop.c

29行目の"REDHAT_KERNEL"のdefineのコメントをはずす。

before: 29: /* #define REDHAT_KERNEL */
after: 29: #define REDHAT_KERNEL
(iii)【任意】cloopデバイス最大数の変更

  必要に応じて、cloopデバイスの最大数を変更する。デフォルトは、8。

$ vi compressed_loop.c

26行目の"CLOOP_MAX"の値を必要に応じ変更する。

before: 26 #define CLOOP_MAX 8
after: 26 #define CLOOP_MAX 16
(vi)コンパイル
# make KERNEL_DIR=/usr/src/redhat/BUILD/kernel-2.6.19/linux-2.6.19.i686/
       ※KERNEL_DIR:カーネルソースコードディレクト
(v)モジュールのインストール

   kernel2.6系の場合cloop.koを使用する。
   cloop.koをカーネルライブラリのディレクトリにコピーし、
   モジュールの依存関係をチェックするする。

# mkdir -p /lib/modules/`uname -r`/misc && \
   cp cloop.ko /lib/modules/`uname -r`/misc;
# depmod -a

(4)cloopモジュールの組み込みテスト

 正常にcloopが組み込めるか確認する。

(i)モジュール情報の確認

   cloopのモジュール情報が出力されることを確認

# modinfo cloop
filename: /lib/modules/2.6.19-1.2288.2.1.fc5/misc/cloop.ko
description: Transparently decompressing loopback block device
author: Klaus Knopper (Kernel 2.4 and up, Knoppix version), Paul Russel (initial version)
<以下略>
(ii)モジュールの組み込みテスト

   モジュールの組み込み可否を確認する。

# modprobe cloop
# lsmod|grep cloop
cloop 15264 0 <-表示されることを確認
(iii)cloopデバイスの確認。

    udev機能により、cloopモジュール組み込みときcloopデバイスが自動的に生成される。
    cloopデバイスの有無を確認する。

# ls /dev/cloop*
/dev/cloop0 ・・・ <-表示されることを確認

(5)cloop関連コマンドのコピー

 create_compressed_fsとextract_compressed_fs(cloopソースコード内に梱包)をコピーする。

# install -m0755 create_compressed_fs /usr/local/bin/
# install -m0755 extract_compressed_fs /usr/local/bin/

3.手順(cloopファイルの作成とマウント)

(1)cloopファイルシステムの作成

 下記コマンドで、cloopのファイルシステムにしたいデータがあるディレクトリをcloopのフォーマットに変換する。

# mkisofs -r datadir | \
create_compressed_fs - 65536 > datadir.iso.compressed ;

datadir : 対象データのディレクトリ名
datadir.iso.compressed : cloopフォーマットのファイル名

(2)ファイルシステムのマウント

(i) cloopファイルをcloopデバイスにバインドする

   下記コマンドで、生成したcloopファイルとcloopのデバイスをくくりつける。<例:cloop0デバイスに、iso.compressedファイルを括り付ける>

# losetup /dev/cloop0 /tmp/datadir.iso.compressed
(ii) cloopをマウントする

   ファイルシステムをマウントする。

# mount -o ro -t whatever /dev/cloop0 /mnt/compressed

※OS起動停止時のcloopファイルシステムの自動マウント・アンマウントシェル(rcシェル)はこちらを参照→ 【Fedora/RHEL】cloop自動マウント用rcスクリプト

Fedora/RHEL cloop自動マウント用rcスクリプト

 OS起動停止時に、cloopモジュールロード・アンロードとcloopファイルシステムのマウント・アンマウントを行うrcシェルを作る。
 cloopモジュールの実装は → こちらを参照

1.cloop用シェル

 サンプルシェル(cloop)を以下に示す。

#!/bin/sh
# script to start and stop cloop devices
#        and mount and umount cloop filesystems
#
# chkconfig: 35 60 20
# description: Starts, stops cloop modules
#

# set the maximum number of cloop device
# refer to the MAX_CLOOP in compressed_loop.c
MAX_CLOOP=8

CLOOP_DEV[0]=/dev/cloop0
CLOOP_FIL[0]=/tmp/a.cloop
CLOOP_DIR[0]=/tmp/a

#CLOOP_DEV[1]=/dev/cloop1
#CLOOP_FIL[1]=/dummy1
#CLOOP_DIR[1]=/dummy1

#CLOOP_DEV[2]=/dev/cloop2
#CLOOP_FIL[2]=/dummyr2
#CLOOP_DIR[2]=/dummy2

#CLOOP_DEV[3]=/dev/cloop3
#CLOOP_FIL[3]=/dummy3
#CLOOP_DIR[3]=/dummy3


# Source function library.
. /etc/init.d/functions


function abend(){
  echo ERROR: $@
  failure
  exit 1
}

function load_cloop_module(){

  lsmod |grep cloop > /dev/null
  [ $? -eq 0 ] && (echo "cloop has already been loaded.";return )
  modprobe cloop
  [ $? -ne 0 ] && abend can not load the cloop module. 
 
  i=0; FLAG=FALSE
  while [ $i -lt 10 ]
  do
    sleep 1
    if [ -b "/dev/cloop`expr $MAX_CLOOP - 1`" ]
    then
       FLAG=TRUE; break
    fi
    i=`expr $i + 1`
  done
  [ "$FLAG" != TRUE ] && abend not found the maximum cloop device.

}

function bind_and_mount(){

  # check a cloop device
  [ -b $1 ]     || abend $1 is a invalid cloop device.

  # check a cloop file
  [ -f $2 ]     || abend not found $2 file.
  head -3 $2 | grep cloop 2>&1 > /dev/null
  [ $? -eq 0 ]  || abend $2 is a invalid cloop file.

  # check mount directory
  [ -d $3 ]     || abend $3 is not directory.
 
  losetup $1 $2 || abend can not bind $2 file to $1 device.

  mount -r -t iso9660 $1 $3 || abend can not mount $1 on $3.

}

function start(){
 
  load_cloop_module

  i=0
  while [ -n "${CLOOP_DEV[$i]}" -a \
          -n "${CLOOP_FIL[$i]}" -a \
          -n "${CLOOP_DIR[$i]}" ]
  do
  bind_and_mount ${CLOOP_DEV[$i]} ${CLOOP_FIL[$i]} ${CLOOP_DIR[$i]}
  
  i=`expr $i + 1`
  done
  
  success
  touch /var/lock/subsys/cloop

}

function stop(){

  i=0
  while [ -n "${CLOOP_DEV[$i]}" -a \
          -n "${CLOOP_FIL[$i]}" -a \
          -n "${CLOOP_DIR[$i]}" ]
  do
  umount ${CLOOP_DIR[$i]}
  losetup -d ${CLOOP_DEV[$i]}

  i=`expr $i + 1`
  done
 
  modprobe -r cloop
 
  success
  rm -f /var/lock/subsys/cloop

} 
  

# See how we were called.
case "$1" in
  start)
        start
        ;;
  stop)
        stop
        ;;
  restart|reload)
        stop
        start
        ;;
  *)
        echo $"Usage: $0 {start|stop|restart}"
        exit 1
esac

exit 0

※備考

環境に応じて、下記パラメータを設定する。

(a)MAX_CLOOP=8

→cloopデバイスの最大数。compressed_loop.cの"#define CLOOP_MAX"の値を設定する。

(b)CLOOP_DEV[n],CLOOP_FIL[n],CLOOP_DIR[n]

→マウントするcloopの情報。3つ一体で設定。nは0番から順番に設定する。それぞれの変数は以下の通り。
CLOOP_DEV[n] : cloopファイルシステムを割り当てるcloopデバイス。
CLOOP_FIL[n] : マウントするcloopファイルシステム。フルパス(絶対パス)で指定する。
CLOOP_DIR[n] : マウントポイントのディレクトリをフルパスで指定する。

2.シェルの組み込み

(1)上記のサンプルシェルを/etc/init.dに配置する。

# vi /etc/init.d/cloop
# chown root:root /etc/init.d/cloop
# chmod 755 /etc/init.d/cloop

(2)chkconfigコマンドでOS起動停止処理に組み込む

# chkconfig --add cloop
# chkconfig --list|grep cloop

     ※備考
     chkconfigの設定はスクリプトヘッダの下記部分が該当する。

# chkconfig: 35 60 20
# description: Starts, stops cloop modules